/*-------------------------------------------------------------------------
 *
 * bufmgr.c
 *	  缓冲区管理器接口例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/buffer/bufmgr.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 主要入口点：
 *
 * ReadBuffer() -- 查找或创建一个持有请求页面的缓冲区，并将其固定，以确保在此进程使用它时没有人可以销毁它。
 *
 * ReleaseBuffer() -- 释放一个缓冲区
 *
 * MarkBufferDirty() -- 将固定缓冲区的内容标记为“脏”。
 * 磁盘写入将延迟到缓冲区替换或检查点时。
 *
 * 另请参见以下文件：
 * freelist.c -- 选择缓冲区替换的牺牲品
 * buf_table.c -- 管理缓冲区查找表
 */
#include "postgres.h"

#include <sys/file.h>
#include <unistd.h>

#include "access/tableam.h"
#include "access/xloginsert.h"
#include "access/xlogutils.h"
#include "catalog/catalog.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "executor/instrument.h"
#include "lib/binaryheap.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "storage/smgr.h"
#include "storage/standby.h"
#include "utils/memdebug.h"
#include "utils/ps_status.h"
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/timestamp.h"


/* 注意：这两个宏仅适用于共享缓冲区，而不适用于本地缓冲区！ */
#define BufHdrGetBlock(bufHdr)	((Block) (BufferBlocks + ((Size) (bufHdr)->buf_id) * BLCKSZ))
#define BufferGetLSN(bufHdr)	(PageGetLSN(BufHdrGetBlock(bufHdr)))

/* 注意：此宏仅适用于本地缓冲区，而不适用于共享缓冲区！ */
#define LocalBufHdrGetBlock(bufHdr) \
	LocalBufferBlockPointers[-((bufHdr)->buf_id + 2)]

/* SyncOneBuffer 的返回值中的位 */
#define BUF_WRITTEN				0x01
#define BUF_REUSABLE			0x02

#define RELS_BSEARCH_THRESHOLD		20

/*
 * 这是检查点过程中扫描整个缓冲池以删除所有正在丢弃的关系页面的缓冲区的阈值。
 * 对于小于此阈值的关系，使用 BufMapping 表进行查找以查找缓冲区。
 */
#define BUF_DROP_FULL_SCAN_THRESHOLD		(uint64) (NBuffers / 32)

typedef struct PrivateRefCountEntry
{
	Buffer		buffer;
	int32		refcount;
} PrivateRefCountEntry;

/* 64 字节，约为常见系统的缓存行大小 */
#define REFCOUNT_ARRAY_ENTRIES 8

/*
 * 用于检查点特定表空间的缓冲区状态，在 BufferSync 内部使用。
 */
typedef struct CkptTsStatus
{
	/* 表空间的 oid */
	Oid			tsId;

	/*
 * 此表空间的检查点进度。为了使不同表空间之间的进度可比较，该进度对于每个表空间，以 0 到待检查点页面总数之间的数字进行衡量。
 * 在该表空间中每检查点一个页面，进度增加 progress_slice。
 */
	float8		progress;
	float8		progress_slice;

	/* 在此表空间中待检查点的页面数 */
	int			num_to_scan;
	/* 在此表空间中已处理的页面数 */
	int			num_scanned;

	/* 此表空间的 CkptBufferIds 中的当前偏移量 */
	int			index;
} CkptTsStatus;

/*
 * 用于排序 SMgrRelations 的数组的类型
 *
 * FlushRelationsAllBuffers 与 DropRelFileNodesAllBuffers 共享相同的比较器函数。
 * 指向此结构和 RelFileNode 的指针必须兼容。
 */
typedef struct SMgrSortArray
{
	RelFileNode rnode;			/* 这必须是第一个成员 */
	SMgrRelation srel;
} SMgrSortArray;

/* GUC 变量 */
bool		zero_damaged_pages = false;
int			bgwriter_lru_maxpages = 100;
double		bgwriter_lru_multiplier = 2.0;
bool		track_io_timing = false;

/*
 * PrefetchBuffer 调用者应该尝试超前多少个缓冲区以满足他们的
 * ReadBuffer 调用。零意味着“永不预取”。该值仅用于不属于
 * 有效_io_并发参数已设置的表空间的缓冲区。
 */
int			effective_io_concurrency = 0;

/*
 * 类似于有效_io_并发，但由可能从更高设置中受益的维护代码路径使用，
 * 因为它们代表多个会话工作。
 * 被同名的表空间设置覆盖。
 */
int			maintenance_io_concurrency = 0;

/*
 * 关于触发内核写回以便写入缓冲区的 GUC 变量；操作系统
 * 相关的默认值通过 GUC 机制设置。
 */
int			checkpoint_flush_after = 0;
int			bgwriter_flush_after = 0;
int			backend_flush_after = 0;

/* StartBufferIO 和相关函数的局部状态 */
static BufferDesc *InProgressBuf = NULL;
static bool IsForInput;

/* LockBufferForCleanup 的局部状态 */
static BufferDesc *PinCountWaitBuf = NULL;

/*
 * 后端专用引用计数管理：
 *
 * 每个缓冲区还有一个私人引用计数，跟踪当前进程中该缓冲区固定的
 * 次数。这是为了让共享引用计数只需在一个后端中被修改一次，
 * 如果一个缓冲区被单个后端固定次数超过一次。它还用于检查在事务结束时和
 * 退出时没有缓冲区仍处于固定状态。
 *
 *
 * 为了避免像我们过去一样，需要一个具有 NBuffers 条目的数组来跟踪
 * 本地缓冲区，我们使用一个小的顺序搜索数组
 * (PrivateRefCountArray)和一个溢出哈希表（PrivateRefCountHash）来
 * 跟踪后端本地固定。
 *
 * 直到不超过 REFCOUNT_ARRAY_ENTRIES 的缓冲区被一次固定，所有的
 * 引用计数都在数组中进行跟踪；之后，新数组条目将旧条目移入哈希表。
 * 这样，频繁使用的条目不会被“卡住”在哈希表中，而不常用的条目不会堵塞数组。
 *
 * 请注意，在大多数情况下，固定的缓冲区数量不会超过
 * REFCOUNT_ARRAY_ENTRIES。
 *
 *
 * 要将缓冲区输入到引用计数跟踪机制中，首先使用 ReservePrivateRefCountEntry()
 * 保留一个空闲条目，然后在必要时使用 NewPrivateRefCountEntry() 填充它。
 * 这样的分离让我们避免在 NewPrivateRefCountEntry() 中进行内存分配，
 * 这可能很重要，因为在某些情况下它是在持有自旋锁的情况下调用的...
 */
static struct PrivateRefCountEntry PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES];
static HTAB *PrivateRefCountHash = NULL;
static int32 PrivateRefCountOverflowed = 0;
static uint32 PrivateRefCountClock = 0;
static PrivateRefCountEntry *ReservedRefCountEntry = NULL;

static void fc_ReservePrivateRefCountEntry(void);
static PrivateRefCountEntry *fc_NewPrivateRefCountEntry(Buffer fc_buffer);
static PrivateRefCountEntry *fc_GetPrivateRefCountEntry(Buffer fc_buffer, bool fc_do_move);
static inline int32 fc_GetPrivateRefCount(Buffer fc_buffer);
static void fc_ForgetPrivateRefCountEntry(PrivateRefCountEntry *fc_ref);

/*
 * 确保 PrivateRefCountArray 有足够的空间存储一个条目。
 * 必须在使用 NewPrivateRefCountEntry() 填充新条目之前调用此方法 -
 * 但不使用保留条目也可以完全可以。
 */
static void fc_ReservePrivateRefCountEntry(void)
{
	/* 已经保留（或释放），无事可做 */
	if (ReservedRefCountEntry != NULL)
		return;

	/*
	 * 首先在数组中搜索一个空闲条目，在大多数情况下这就足够了。
	 */
	{
		int			fc_i;

		for (fc_i = 0; fc_i < REFCOUNT_ARRAY_ENTRIES; fc_i++)
		{
			PrivateRefCountEntry *fc_res;

			fc_res = &PrivateRefCountArray[fc_i];

			if (fc_res->buffer == InvalidBuffer)
			{
				ReservedRefCountEntry = fc_res;
				return;
			}
		}
	}

	/*
	 * 不成功。所有数组条目都已满。将一个数组条目移入哈希
	 * 表。
	 */
	{
		/*
		 * 将当前时钟位置的条目从数组移动到
		 * 哈希表。使用这个槽。
		 */
		PrivateRefCountEntry *fc_hashent;
		bool		fc_found;

		/* 选择受害者槽 */
		ReservedRefCountEntry =
			&PrivateRefCountArray[PrivateRefCountClock++ % REFCOUNT_ARRAY_ENTRIES];

		/* 最好被使用，否则我们不该来到这里。 */
		Assert(ReservedRefCountEntry->buffer != InvalidBuffer);

		/* 将受害者数组条目输入哈希表 */
		fc_hashent = hash_search(PrivateRefCountHash,
							  (void *) &(ReservedRefCountEntry->buffer),
							  HASH_ENTER,
							  &fc_found);
		Assert(!fc_found);
		fc_hashent->refcount = ReservedRefCountEntry->refcount;

		/* 清除现在空闲的数组槽 */
		ReservedRefCountEntry->buffer = InvalidBuffer;
		ReservedRefCountEntry->refcount = 0;

		PrivateRefCountOverflowed++;
	}
}

/*
 * 填充一个先前保留的引用计数条目。
 */
static PrivateRefCountEntry *
fc_NewPrivateRefCountEntry(Buffer fc_buffer)
{
	PrivateRefCountEntry *fc_res;

	/* 仅在已进行预留时允许调用 */
	Assert(ReservedRefCountEntry != NULL);

	/* 使用已保留条目 */
	fc_res = ReservedRefCountEntry;
	ReservedRefCountEntry = NULL;

	/* 并填充它 */
	fc_res->buffer = fc_buffer;
	fc_res->refcount = 0;

	return fc_res;
}

/*
 * 返回传入缓冲区的 PrivateRefCount 条目。
 *
 * 如果缓冲区没有引用计数条目，则返回 NULL。否则，如果
 * do_move 为 true，并且该条目位于哈希表中，则通过将其移动到
 * 数组来优化该条目以便于频繁访问。
 */
static PrivateRefCountEntry *
fc_GetPrivateRefCountEntry(Buffer fc_buffer, bool fc_do_move)
{
	PrivateRefCountEntry *fc_res;
	int			fc_i;

	Assert(BufferIsValid(fc_buffer));
	Assert(!BufferIsLocal(fc_buffer));

	/*
	 * 首先在数组中搜索引用，在大多数情况下这就足够了。
	 */
	for (fc_i = 0; fc_i < REFCOUNT_ARRAY_ENTRIES; fc_i++)
	{
		fc_res = &PrivateRefCountArray[fc_i];

		if (fc_res->buffer == fc_buffer)
			return fc_res;
	}

	/*
	 * 到这里我们知道，如果缓冲区已经被固定，就不在数组中。
	 *
	 * 只有在我们之前溢出到哈希表时才在哈希表中查找缓冲区。
	 */
	if (PrivateRefCountOverflowed == 0)
		return NULL;

	fc_res = hash_search(PrivateRefCountHash,
					  (void *) &fc_buffer,
					  HASH_FIND,
					  NULL);

	if (fc_res == NULL)
		return NULL;
	else if (!fc_do_move)
	{
		/* 调用者不希望我们将哈希条目移动到数组中 */
		return fc_res;
	}
	else
	{
		/* 将缓冲区从哈希表移动到空闲数组槽 */
		bool		fc_found;
		PrivateRefCountEntry *fc_free;

		/* 确保有一个空闲数组槽 */
		fc_ReservePrivateRefCountEntry();

		/* 使用已保留槽 */
		Assert(ReservedRefCountEntry != NULL);
		fc_free = ReservedRefCountEntry;
		ReservedRefCountEntry = NULL;
		Assert(fc_free->buffer == InvalidBuffer);

		/* 并填充它 */
		fc_free->buffer = fc_buffer;
		fc_free->refcount = fc_res->refcount;

		/* 从哈希表中删除 */
		hash_search(PrivateRefCountHash,
					(void *) &fc_buffer,
					HASH_REMOVE,
					&fc_found);
		Assert(fc_found);
		Assert(PrivateRefCountOverflowed > 0);
		PrivateRefCountOverflowed--;

		return fc_free;
	}
}

/*
 * 返回该缓冲区被此后端固定的次数。
 *
 * 仅适用于共享内存缓冲区！
 */
static inline int32
fc_GetPrivateRefCount(Buffer fc_buffer)
{
	PrivateRefCountEntry *fc_ref;

	Assert(BufferIsValid(fc_buffer));
	Assert(!BufferIsLocal(fc_buffer));

	/*
	 * 不移动条目 - 对当前用户来说没问题，但我们可能会想有一天改变这个。
	 */
	fc_ref = fc_GetPrivateRefCountEntry(fc_buffer, false);

	if (fc_ref == NULL)
		return 0;
	return fc_ref->refcount;
}

/*
 * 释放用于跟踪已解除固定缓冲区的引用计数所使用的资源，我们不再想立即固定它。
 */
static void fc_ForgetPrivateRefCountEntry(PrivateRefCountEntry *fc_ref)
{
	Assert(fc_ref->refcount == 0);

	if (fc_ref >= &PrivateRefCountArray[0] &&
		fc_ref < &PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES])
	{
		fc_ref->buffer = InvalidBuffer;

		/*
		 * 将刚刚使用的条目标记为保留 - 在许多情况下，这允许我们避免搜索数组/哈希以寻找空闲条目。
		 */
		ReservedRefCountEntry = fc_ref;
	}
	else
	{
		bool		fc_found;
		Buffer		fc_buffer = fc_ref->buffer;

		hash_search(PrivateRefCountHash,
					(void *) &fc_buffer,
					HASH_REMOVE,
					&fc_found);
		Assert(fc_found);
		Assert(PrivateRefCountOverflowed > 0);
		PrivateRefCountOverflowed--;
	}
}

/*
 * BufferIsPinned
 *		仅当缓冲区被固定时为真（也检查缓冲区编号是否有效）。
 *
 *		注意：我们在这里检查的是*这个*后端是否持有对缓冲区的固定。我们不关心其他后端是否持有。
 */
#define BufferIsPinned(bufnum) \
( \
	!BufferIsValid(bufnum) ? \
		false \
	: \
		BufferIsLocal(bufnum) ? \
			(LocalRefCount[-(bufnum) - 1] > 0) \
		: \
	(fc_GetPrivateRefCount(bufnum) > 0) \
)


static Buffer fc_ReadBuffer_common(SMgrRelation fc_reln, char fc_relpersistence,
								ForkNumber fc_forkNum, BlockNumber fc_blockNum,
								ReadBufferMode fc_mode, BufferAccessStrategy fc_strategy,
								bool *fc_hit);
static bool fc_PinBuffer(BufferDesc *fc_buf, BufferAccessStrategy fc_strategy);
static void fc_PinBuffer_Locked(BufferDesc *fc_buf);
static void fc_UnpinBuffer(BufferDesc *fc_buf, bool fc_fixOwner);
static void fc_BufferSync(int fc_flags);
static uint32 fc_WaitBufHdrUnlocked(BufferDesc *fc_buf);
static int	fc_SyncOneBuffer(int fc_buf_id, bool fc_skip_recently_used,
						  WritebackContext *fc_wb_context);
static void fc_WaitIO(BufferDesc *fc_buf);
static bool fc_StartBufferIO(BufferDesc *fc_buf, bool fc_forInput);
static void fc_TerminateBufferIO(BufferDesc *fc_buf, bool fc_clear_dirty,
							  uint32 set_flag_bits);
static void fc_shared_buffer_write_error_callback(void *fc_arg);
static void fc_local_buffer_write_error_callback(void *fc_arg);
static BufferDesc *fc_BufferAlloc(SMgrRelation fc_smgr,
							   char fc_relpersistence,
							   ForkNumber fc_forkNum,
							   BlockNumber fc_blockNum,
							   BufferAccessStrategy fc_strategy,
							   bool *fc_foundPtr);
static void fc_FlushBuffer(BufferDesc *fc_buf, SMgrRelation fc_reln);
static void fc_FindAndDropRelFileNodeBuffers(RelFileNode fc_rnode,
										  ForkNumber fc_forkNum,
										  BlockNumber fc_nForkBlock,
										  BlockNumber fc_firstDelBlock);
static void fc_RelationCopyStorageUsingBuffer(RelFileNode fc_srcnode,
										   RelFileNode fc_dstnode,
										   ForkNumber fc_forkNum, bool fc_permanent);
static void fc_AtProcExit_Buffers(int fc_code, Datum fc_arg);
static void fc_CheckForBufferLeaks(void);
static int	fc_rnode_comparator(const void *fc_p1, const void *fc_p2);
static inline int fc_buffertag_comparator(const BufferTag *fc_a, const BufferTag *fc_b);
static inline int fc_ckpt_buforder_comparator(const CkptSortItem *fc_a, const CkptSortItem *fc_b);
static int	fc_ts_ckpt_progress_comparator(Datum fc_a, Datum fc_b, void *fc_arg);


/*
 * Shared buffers的PrefetchBuffer()实现。
 */
PrefetchBufferResult PrefetchSharedBuffer(SMgrRelation fc_smgr_reln,
					 ForkNumber fc_forkNum,
					 BlockNumber fc_blockNum)
{
	PrefetchBufferResult fc_result = {InvalidBuffer, false};
	BufferTag	fc_newTag;			/* 请求块的标识 */
	uint32		fc_newHash;		/* newTag的哈希值 */
	LWLock	   *fc_newPartitionLock;	/* 其缓冲区分区锁 */
	int			fc_buf_id;

	Assert(BlockNumberIsValid(fc_blockNum));

	/* 创建一个标签以便我们可以查找缓冲区 */
	INIT_BUFFERTAG(fc_newTag, fc_smgr_reln->smgr_rnode.node,
				   fc_forkNum, fc_blockNum);

	/* 确定其哈希代码和分区锁ID */
	fc_newHash = BufTableHashCode(&fc_newTag);
	fc_newPartitionLock = BufMappingPartitionLock(fc_newHash);

	/* 查看块是否已经在缓冲池中 */
	LWLockAcquire(fc_newPartitionLock, LW_SHARED);
	fc_buf_id = BufTableLookup(&fc_newTag, fc_newHash);
	LWLockRelease(fc_newPartitionLock);

	/* 如果不在缓冲区中，启动预取 */
	if (fc_buf_id < 0)
	{
#ifdef USE_PREFETCH
		/*
		 * 尝试启动异步读取。如果关系文件不存在，这将在恢复中返回false。
		 */
		if (smgrprefetch(fc_smgr_reln, fc_forkNum, fc_blockNum))
			fc_result.initiated_io = true;
#endif							/* USE_PREFETCH */
	}
	else
	{
		/*
		 * 报告当时所在的缓冲区。调用者可能能够避免缓冲区表查找，但它没有被固定，必须重新检查！
		 */
		fc_result.recent_buffer = fc_buf_id + 1;
	}

	/*
	 * 如果块*在*缓冲区中，我们什么也不做。这并不理想：
	 * 块可能正要被驱逐，这会很愚蠢，因为我们知道我们很快会需要它。
	 * 但唯一的简单解决方案是增加usage_count，这似乎不是一个好解决方案：
	 * 当调用者最终接触到该块时，usage_count会再次增加，导致对参与预取序列的块过于偏爱。
	 * 真正的解决方案需要涉及一些额外的每个缓冲区状态，并且不清楚是否有足够的问题来证明这一点。
	 */

	return fc_result;
}

/*
 * PrefetchBuffer -- 启动对关系块的异步读取
 *
 * 这与ReadBuffer相似，但实际上并不分配缓冲区。
 * 相反，它试图确保未来对给定块的ReadBuffer不会因I/O而延迟。预取是可选的。
 *
 * 可能有三种结果：
 *
 * 1. 如果块已经缓存，结果包括一个有效的缓冲区，调用者可以使用它以避免需要后续的缓冲查找，但它没有被固定，因此调用者必须重新检查。
 *
 * 2. 如果内核被请求启动I/O，initiated_io成员为真。
 * 目前没有办法知道数据是否已经被内核缓存，因此实际上没有真正启动I/O，也没有办法知道I/O何时完成，除了使用同步ReadBuffer()。
 *
 * 3. 否则，缓冲区尚未被PostgreSQL缓存，或者未定义USE_PREFETCH（此构建由于缺乏内核设施而不支持预取），或者未找到基础关系文件并且我们处于恢复中。
 * （如果未找到关系文件并且我们不在恢复中，将引发错误）。
 */
PrefetchBufferResult PrefetchBuffer(Relation fc_reln, ForkNumber fc_forkNum, BlockNumber fc_blockNum)
{
	Assert(RelationIsValid(fc_reln));
	Assert(BlockNumberIsValid(fc_blockNum));

	if (RelationUsesLocalBuffers(fc_reln))
	{
		/* 请参阅ReadBufferExtended中的注释 */
		if (RELATION_IS_OTHER_TEMP(fc_reln))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot access temporary tables of other sessions")));

		/* 传递给localbuf.c */
		return PrefetchLocalBuffer(RelationGetSmgr(fc_reln), fc_forkNum, fc_blockNum);
	}
	else
	{
		/* 传递给共享缓冲区版本 */
		return PrefetchSharedBuffer(RelationGetSmgr(fc_reln), fc_forkNum, fc_blockNum);
	}
}

/*
 * ReadRecentBuffer -- 尝试在最近观察到的缓冲区中固定块
 *
 * 与ReadBuffer()相比，这在成功时避免了缓冲映射查找。
 * 如果缓冲区有效并且仍然具有预期的标签，则返回true。在这种情况下，缓冲区被固定，使用计数增加。
 */
bool ReadRecentBuffer(RelFileNode fc_rnode, ForkNumber fc_forkNum, BlockNumber fc_blockNum,
				 Buffer fc_recent_buffer)
{
	BufferDesc *fc_bufHdr;
	BufferTag	fc_tag;
	uint32		fc_buf_state;
	bool		fc_have_private_ref;

	Assert(BufferIsValid(fc_recent_buffer));

	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
	fc_ReservePrivateRefCountEntry();
	INIT_BUFFERTAG(fc_tag, fc_rnode, fc_forkNum, fc_blockNum);

	if (BufferIsLocal(fc_recent_buffer))
	{
		int			fc_b = -fc_recent_buffer - 1;

		fc_bufHdr = GetLocalBufferDescriptor(fc_b);
		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

		/* 它仍然有效并持有正确的标签吗？ */
		if ((fc_buf_state & BM_VALID) && BUFFERTAGS_EQUAL(fc_tag, fc_bufHdr->tag))
		{
			/*
			 * 增加缓冲区的引用和使用计数。这相当于对共享缓冲区的PinBuffer。
			 */
			if (LocalRefCount[fc_b] == 0)
			{
				if (BUF_STATE_GET_USAGECOUNT(fc_buf_state) < BM_MAX_USAGE_COUNT)
				{
					fc_buf_state += BUF_USAGECOUNT_ONE;
					pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
				}
			}
			LocalRefCount[fc_b]++;
			ResourceOwnerRememberBuffer(CurrentResourceOwner, fc_recent_buffer);

			pgBufferUsage.local_blks_hit++;

			return true;
		}
	}
	else
	{
		fc_bufHdr = GetBufferDescriptor(fc_recent_buffer - 1);
		fc_have_private_ref = fc_GetPrivateRefCount(fc_recent_buffer) > 0;

		/*
		 * 我们是否已经用私有引用固定了此缓冲区？如果是，它必须有效，可以安全检查标签而无需加锁。
		 * 如果不是，我们必须先锁定头，然后再检查。
		 */
		if (fc_have_private_ref)
			fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);
		else
			fc_buf_state = LockBufHdr(fc_bufHdr);

		if ((fc_buf_state & BM_VALID) && BUFFERTAGS_EQUAL(fc_tag, fc_bufHdr->tag))
		{
			/*
			 * 现在安全固定缓冲区。我们不能先固定再问问题，因为如果我们固定了一个随机的不匹配缓冲区，可能会混淆代码路径，例如InvalidateBuffer()。
			 */
			if (fc_have_private_ref)
				fc_PinBuffer(fc_bufHdr, NULL);	/* 增加固定计数 */
			else
				fc_PinBuffer_Locked(fc_bufHdr);	/* 第一次固定 */

			pgBufferUsage.shared_blks_hit++;

			return true;
		}

		/* 如果我们在上面锁定了头部，现在解锁。 */
		if (!fc_have_private_ref)
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}

	return false;
}

/*
 * ReadBuffer -- 用于从主分叉以RBM_NORMAL模式和默认策略读取的ReadBufferExtended的简写。
 */
Buffer ReadBuffer(Relation fc_reln, BlockNumber fc_blockNum)
{
	return ReadBufferExtended(fc_reln, MAIN_FORKNUM, fc_blockNum, RBM_NORMAL, NULL);
}

/*
 * ReadBufferExtended -- 返回一个缓冲区，包含请求的
 *		关系的请求块。如果请求的 blknum
 *		为 P_NEW，则扩展关系文件并
 *		分配一个新块。（调用者负责
 *		确保只有一个后端尝试同时扩展一个
 *		关系！）
 *
 * 返回：包含读取块的缓冲区编号。
 *		返回的缓冲区已经被固定。
 *		出错时不返回——而是记录错误。
 *
 * 假设在调用此函数时，reln 已经打开。
 *
 * 在 RBM_NORMAL 模式下，页面从磁盘读取，页面头部
 * 经验证。如果页面头部无效则会抛出错误。
 * （但请注意，所有零的页面被认为是“有效的”；见
 * PageIsVerifiedExtended()。）
 *
 * RBM_ZERO_ON_ERROR 类似于正常模式，但如果页面头部无效，
 * 则将页面清零，而不是抛出错误。这是针对非关键数据的，
 * 调用者做好了修复错误的准备。
 *
 * 在 RBM_ZERO_AND_LOCK 模式下，如果页面尚未在缓冲区缓存中，
 * 则将其填充为零，而不是从磁盘读取。这在调用者将要
 * 从头填充页面时非常有用，因为这可以节省I/O并避免
 * 在页面上的磁盘有损坏页面头的时候产生不必要的失败。
 * 此页面在返回时被锁定，以确保调用者有机会
 * 在其他人可见之前初始化该页面。
 * 注意：不要使用此模式读取超出关系
 * 当前物理 EOF 的页面；在修改并写出页面时，这
 * 很可能会在 md.c 中导致问题。不过 P_NEW 是可以的。
 *
 * RBM_ZERO_AND_CLEANUP_LOCK 与 RBM_ZERO_AND_LOCK 相同，但在
 * 页面上获取清理强度锁。
 *
 * RBM_NORMAL_NO_LOG 模式在这里与 RBM_NORMAL 的处理相同。
 *
 * 如果 strategy 不为 NULL，则使用非默认的缓冲区访问策略。
 * 有关详细信息，请参阅 buffer/README。
 */
Buffer ReadBufferExtended(Relation fc_reln, ForkNumber fc_forkNum, BlockNumber fc_blockNum,
				   ReadBufferMode fc_mode, BufferAccessStrategy fc_strategy)
{
	bool		fc_hit;
	Buffer		fc_buf;

	/*
	 * 拒绝尝试读取非本地临时关系；我们可能会获取错误的数据，因为
	 * 我们无法查看拥有会话的本地缓冲区。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_reln))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot access temporary tables of other sessions")));

	/*
	 * 读取缓冲区，并更新 pgstat 计数器以反映缓存命中或
	 * 丢失。
	 */
	pgstat_count_buffer_read(fc_reln);
	fc_buf = fc_ReadBuffer_common(RelationGetSmgr(fc_reln), fc_reln->rd_rel->relpersistence,
							fc_forkNum, fc_blockNum, fc_mode, fc_strategy, &fc_hit);
	if (fc_hit)
		pgstat_count_buffer_hit(fc_reln);
	return fc_buf;
}


/*
 * ReadBufferWithoutRelcache -- 类似于 ReadBufferExtended，但不需要
 *		关系的 relcache 条目。
 *
 * 对于 RELPERSISTENCE_PERMANENT 关系传递 permanent = true，
 * 对于 RELPERSISTENCE_UNLOGGED 关系传递 permanent = false。
 * 此函数不能用于临时关系（而使其正常工作可能会很困难，
 * 除非我们只想为自己的 BackendId 读取临时关系）。
 */
Buffer ReadBufferWithoutRelcache(RelFileNode fc_rnode, ForkNumber fc_forkNum,
						  BlockNumber fc_blockNum, ReadBufferMode fc_mode,
						  BufferAccessStrategy fc_strategy, bool fc_permanent)
{
	bool		fc_hit;

	SMgrRelation fc_smgr = smgropen(fc_rnode, InvalidBackendId);

	return fc_ReadBuffer_common(fc_smgr, fc_permanent ? RELPERSISTENCE_PERMANENT :
							 RELPERSISTENCE_UNLOGGED, fc_forkNum, fc_blockNum,
							 fc_mode, fc_strategy, &fc_hit);
}


/*
 * ReadBuffer_common -- 所有 ReadBuffer 变体的共同逻辑
 *
 * *hit 如果请求从共享缓冲区缓存满足，则设置为 true。
 */
static Buffer fc_ReadBuffer_common(SMgrRelation fc_smgr, char fc_relpersistence, ForkNumber fc_forkNum,
				  BlockNumber fc_blockNum, ReadBufferMode fc_mode,
				  BufferAccessStrategy fc_strategy, bool *fc_hit)
{
	BufferDesc *fc_bufHdr;
	Block		fc_bufBlock;
	bool		fc_found;
	bool		fc_isExtend;
	bool		fc_isLocalBuf = SmgrIsTemp(fc_smgr);

	*fc_hit = false;

	/* 确保我们有空间来记住缓冲区固定 */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	fc_isExtend = (fc_blockNum == P_NEW);

	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
									   smgr->smgr_rnode.node.spcNode,
									   smgr->smgr_rnode.node.dbNode,
									   smgr->smgr_rnode.node.relNode,
									   smgr->smgr_rnode.backend,
									   isExtend);

	/* 如果调用者请求 P_NEW，则替换为适当的块编号 */
	if (fc_isExtend)
	{
		fc_blockNum = smgrnblocks(fc_smgr, fc_forkNum);
		/* 如果关系已经达到可能的最大长度，则失败 */
		if (fc_blockNum == P_NEW)
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("cannot extend relation %s beyond %u blocks",
							relpath(fc_smgr->smgr_rnode, fc_forkNum),
							P_NEW)));
	}

	if (fc_isLocalBuf)
	{
		fc_bufHdr = LocalBufferAlloc(fc_smgr, fc_forkNum, fc_blockNum, &fc_found);
		if (fc_found)
			pgBufferUsage.local_blks_hit++;
		else if (fc_isExtend)
			pgBufferUsage.local_blks_written++;
		else if (fc_mode == RBM_NORMAL || fc_mode == RBM_NORMAL_NO_LOG ||
				 fc_mode == RBM_ZERO_ON_ERROR)
			pgBufferUsage.local_blks_read++;
	}
	else
	{
		/*
		 * 查找缓冲区。如果请求的块当前不在内存中，则设置
		 * IO_IN_PROGRESS。
		 */
		fc_bufHdr = fc_BufferAlloc(fc_smgr, fc_relpersistence, fc_forkNum, fc_blockNum,
							 fc_strategy, &fc_found);
		if (fc_found)
			pgBufferUsage.shared_blks_hit++;
		else if (fc_isExtend)
			pgBufferUsage.shared_blks_written++;
		else if (fc_mode == RBM_NORMAL || fc_mode == RBM_NORMAL_NO_LOG ||
				 fc_mode == RBM_ZERO_ON_ERROR)
			pgBufferUsage.shared_blks_read++;
	}

	/* 此时我们不持有任何锁。 */

	/* 如果它已经在缓冲池中，我们就完成了 */
	if (fc_found)
	{
		if (!fc_isExtend)
		{
			/* 只需在退出之前更新统计信息 */
			*fc_hit = true;
			VacuumPageHit++;

			if (VacuumCostActive)
				VacuumCostBalance += VacuumCostPageHit;

			TRACE_POSTGRESQL_BUFFER_READ_DONE(forkNum, blockNum,
											  smgr->smgr_rnode.node.spcNode,
											  smgr->smgr_rnode.node.dbNode,
											  smgr->smgr_rnode.node.relNode,
											  smgr->smgr_rnode.backend,
											  isExtend,
											  found);

			/*
			 * 在 RBM_ZERO_AND_LOCK 模式下，调用者期望页面在返回时
			 * 被锁定。
			 */
			if (!fc_isLocalBuf)
			{
				if (fc_mode == RBM_ZERO_AND_LOCK)
					LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr),
								  LW_EXCLUSIVE);
				else if (fc_mode == RBM_ZERO_AND_CLEANUP_LOCK)
					LockBufferForCleanup(BufferDescriptorGetBuffer(fc_bufHdr));
			}

			return BufferDescriptorGetBuffer(fc_bufHdr);
		}

		/*
		 * 我们只有在一个特殊情况下才会到这里，即我们正在尝试扩展
		 * 关系，但我们发现有一个标记为 BM_VALID 的先前存在的缓冲区。
		 * 这可能是因为 mdread 不会抱怨超出 EOF 的读取（当 zero_damaged_pages 打开时），
		 * 所以前一次尝试在 EOF 之外读取块可能留下了一个“有效”的零填充
		 * 缓冲区。不幸的是，我们也遇到过由于有缺陷的 Linux 内核而
		 * 有时返回一个不考虑最近写入的 lseek(SEEK_END) 结果的情况。
		 * 在那种情况下，先前存在的缓冲区可能包含我们不想覆盖的有效数据。
		 * 由于合法的情况应该始终留下一个零填充的缓冲区，
		 * 如果不是 PageIsNew，则会发出警告。
		 */
		fc_bufBlock = fc_isLocalBuf ? LocalBufHdrGetBlock(fc_bufHdr) : BufHdrGetBlock(fc_bufHdr);
		if (!PageIsNew((Page) fc_bufBlock))
			ereport(ERROR,
					(errmsg("unexpected data beyond EOF in block %u of relation %s",
							fc_blockNum, relpath(fc_smgr->smgr_rnode, fc_forkNum)),
					 errhint("This has been seen to occur with buggy kernels; consider updating your system.")));

		/*
		 * 我们 *必须* 在成功之前执行 smgrextend，否则页面将不
		 * 会被内核保留，下一次 P_NEW 调用将决定返回同一页面。
		 * 清除 BM_VALID 位，执行 StartBufferIO
		 * 调用 BufferAlloc 没有做的事情，然后继续。
		 */
		if (fc_isLocalBuf)
		{
			/* 只需调整标志 */
			uint32		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

			Assert(fc_buf_state & BM_VALID);
			fc_buf_state &= ~BM_VALID;
			pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
		}
		else
		{
			/*
			 * 循环处理非常小的可能性，即有人在我们的清除
			 * 之间重新设置 BM_VALID 并让 StartBufferIO 检查它。
			 */
			do
			{
				uint32		fc_buf_state = LockBufHdr(fc_bufHdr);

				Assert(fc_buf_state & BM_VALID);
				fc_buf_state &= ~BM_VALID;
				UnlockBufHdr(fc_bufHdr, fc_buf_state);
			} while (!fc_StartBufferIO(fc_bufHdr, true));
		}
	}

	
/*
	 * 如果我们已达到这一点，我们已经为页面分配了一个缓冲区，但其内容尚未有效。如果这是一个共享缓冲区，IO_IN_PROGRESS 被设置为它。
	 *
	 * 注意：如果 smgrextend 失败，我们将得到一个已分配但未标记为 BM_VALID 的缓冲区。P_NEW 仍将选择相同的块号（因为关系在磁盘上并没有变长），因此将来扩展关系的尝试将找到相同的缓冲区（如果它没有被回收），但将再次回到这里尝试 smgrextend。
	 */
	Assert(!(pg_atomic_read_u32(&fc_bufHdr->state) & BM_VALID));	/* 不需要自旋锁 */

	fc_bufBlock = fc_isLocalBuf ? LocalBufHdrGetBlock(fc_bufHdr) : BufHdrGetBlock(fc_bufHdr);

	if (fc_isExtend)
	{
		/* 新缓冲区被填充为零 */
		MemSet((char *) fc_bufBlock, 0, BLCKSZ);
		/* 不要为全零页面设置校验和 */
		smgrextend(fc_smgr, fc_forkNum, fc_blockNum, (char *) fc_bufBlock, false);

		/*
		 * 注意：我们*没有*在这里进行 ScheduleBufferTagForWriteback；
		 * 尽管我们实际上是在执行写入。但至少在 linux 上这样做会导致 '延迟分配' 机制失效，从而导致文件碎片增加。
		 */
	}
	else
	{
		/*
		 * 读取页面，除非调用者打算覆盖它并且仅希望我们分配一个缓冲区。
		 */
		if (fc_mode == RBM_ZERO_AND_LOCK || fc_mode == RBM_ZERO_AND_CLEANUP_LOCK)
			MemSet((char *) fc_bufBlock, 0, BLCKSZ);
		else
		{
			instr_time	fc_io_start,
						fc_io_time;

			if (track_io_timing)
				INSTR_TIME_SET_CURRENT(fc_io_start);

			smgrread(fc_smgr, fc_forkNum, fc_blockNum, (char *) fc_bufBlock);

			if (track_io_timing)
			{
				INSTR_TIME_SET_CURRENT(fc_io_time);
				INSTR_TIME_SUBTRACT(fc_io_time, fc_io_start);
				pgstat_count_buffer_read_time(INSTR_TIME_GET_MICROSEC(fc_io_time));
				INSTR_TIME_ADD(pgBufferUsage.blk_read_time, fc_io_time);
			}

			/* 检查垃圾数据 */
			if (!PageIsVerifiedExtended((Page) fc_bufBlock, fc_blockNum,
										PIV_LOG_WARNING | PIV_REPORT_STAT))
			{
				if (fc_mode == RBM_ZERO_ON_ERROR || zero_damaged_pages)
				{
					ereport(WARNING,
							(errcode(ERRCODE_DATA_CORRUPTED),
							 errmsg("invalid page in block %u of relation %s; zeroing out page",
									fc_blockNum,
									relpath(fc_smgr->smgr_rnode, fc_forkNum))));
					MemSet((char *) fc_bufBlock, 0, BLCKSZ);
				}
				else
					ereport(ERROR,
							(errcode(ERRCODE_DATA_CORRUPTED),
							 errmsg("invalid page in block %u of relation %s",
									fc_blockNum,
									relpath(fc_smgr->smgr_rnode, fc_forkNum))));
			}
		}
	}

	/*
	 * 在 RBM_ZERO_AND_LOCK 模式下，在将页面标记为有效之前获取缓冲区内容锁，以确保没有其他后端在调用者有机会初始化它之前看到该零填充页面。
	 *
	 * 由于没有其他人可以查看页面内容，因此独占锁和清理强度锁之间没有区别。（注意，我们不能在这里使用 LockBuffer() 或 LockBufferForCleanup()，因为它们断言缓冲区已经有效。）
	 */
	if ((fc_mode == RBM_ZERO_AND_LOCK || fc_mode == RBM_ZERO_AND_CLEANUP_LOCK) &&
		!fc_isLocalBuf)
	{
		LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr), LW_EXCLUSIVE);
	}

	if (fc_isLocalBuf)
	{
		/* 只需调整标志 */
		uint32		fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);

		fc_buf_state |= BM_VALID;
		pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);
	}
	else
	{
		/* 设置 BM_VALID，终止 IO，并唤醒任何等待者 */
		fc_TerminateBufferIO(fc_bufHdr, false, BM_VALID);
	}

	VacuumPageMiss++;
	if (VacuumCostActive)
		VacuumCostBalance += VacuumCostPageMiss;

	TRACE_POSTGRESQL_BUFFER_READ_DONE(forkNum, blockNum,
									  smgr->smgr_rnode.node.spcNode,
									  smgr->smgr_rnode.node.dbNode,
									  smgr->smgr_rnode.node.relNode,
									  smgr->smgr_rnode.backend,
									  isExtend,
									  found);

	return BufferDescriptorGetBuffer(fc_bufHdr);
}

/*
 * BufferAlloc -- ReadBuffer 的子例程。处理共享缓冲区的查找。如果还不存在缓冲区，选择一个替换受害者并逐出旧页面，但不读取新页面。
 *
 * “strategy”可以是一个缓冲区替换策略对象，或者为 NULL 表示默认策略。使用默认策略时，选定缓冲区的使用计数会增加，但否则可能不会（参见 PinBuffer）。
 *
 * 返回的缓冲区被固定，并已标记为持有所需页面。如果它已经有了所需页面，则 *foundPtr 被设置为 true。否则，*foundPtr 被设置为 false，缓冲区被标记为 IO_IN_PROGRESS；ReadBuffer 现在需要执行 I/O 来填充它。
 *
 * *foundPtr 实际上与缓冲区的 BM_VALID 标志是多余的，但我们为了 ReadBuffer 的简便性保留它。
 *
 * 入口和出口时没有持锁。
 */
static BufferDesc *
fc_BufferAlloc(SMgrRelation fc_smgr, char fc_relpersistence, ForkNumber fc_forkNum,
			BlockNumber fc_blockNum,
			BufferAccessStrategy fc_strategy,
			bool *fc_foundPtr)
{
	BufferTag	fc_newTag;			/* 请求块的标识 */
	uint32		fc_newHash;		/* newTag的哈希值 */
	LWLock	   *fc_newPartitionLock;	/* 其缓冲区分区锁 */
	BufferTag	fc_oldTag;			/* 所选缓冲区的先前身份 */
	uint32		fc_oldHash;		/* oldTag 的哈希值 */
	LWLock	   *fc_oldPartitionLock;	/* 其缓冲区分区锁 */
	uint32		fc_oldFlags;
	int			fc_buf_id;
	BufferDesc *fc_buf;
	bool		fc_valid;
	uint32		fc_buf_state;

	/* 创建一个标签以便我们可以查找缓冲区 */
	INIT_BUFFERTAG(fc_newTag, fc_smgr->smgr_rnode.node, fc_forkNum, fc_blockNum);

	/* 确定其哈希代码和分区锁ID */
	fc_newHash = BufTableHashCode(&fc_newTag);
	fc_newPartitionLock = BufMappingPartitionLock(fc_newHash);

	/* 查看块是否已经在缓冲池中 */
	LWLockAcquire(fc_newPartitionLock, LW_SHARED);
	fc_buf_id = BufTableLookup(&fc_newTag, fc_newHash);
	if (fc_buf_id >= 0)
	{
		/*
		 * 找到了。现在，固定缓冲区，以便无人能从缓冲池中偷走它，并检查是否已正确加载数据到缓冲区中。
		 */
		fc_buf = GetBufferDescriptor(fc_buf_id);

		fc_valid = fc_PinBuffer(fc_buf, fc_strategy);

		/* 一旦我们固定了它，就可以释放映射锁 */
		LWLockRelease(fc_newPartitionLock);

		*fc_foundPtr = true;

		if (!fc_valid)
		{
			/*
			 * 我们只能在 (a) 还有其他人正在读取该页面，或者 (b) 之前的读取尝试失败时来到这里。我们必须等待所有活动的读取尝试完成，然后在页面仍然不是 BM_VALID 时设置我们自己的读取尝试。StartBufferIO 完成了所有操作。
			 */
			if (fc_StartBufferIO(fc_buf, true))
			{
				/*
				 * 如果我们来到这里，之前读取缓冲区的尝试一定失败了……但我们将勇敢地再试一次。
				 */
				*fc_foundPtr = false;
			}
		}

		return fc_buf;
	}

	/*
	 * 在缓冲池中没有找到它。我们必须初始化一个新的缓冲区。记得在工作时解锁映射锁。
	 */
	LWLockRelease(fc_newPartitionLock);

	/* 在这里循环，以防我们必须尝试另一个受害缓冲区 */
	for (;;)
	{
		/*
		 * 确保在自旋锁尚未被持有时，有一个空闲的引用计数条目。
		 */
		fc_ReservePrivateRefCountEntry();

		/*
		 * 选择一个受害缓冲区。缓冲区在返回时，其头部自旋锁仍然被持有！
		 */
		fc_buf = StrategyGetBuffer(fc_strategy, &fc_buf_state);

		Assert(BUF_STATE_GET_REFCOUNT(fc_buf_state) == 0);

		/* 必须在仍持有自旋锁时复制缓冲区标志 */
		fc_oldFlags = fc_buf_state & BUF_FLAG_MASK;

		/* 固定缓冲区，然后释放缓冲区自旋锁 */
		fc_PinBuffer_Locked(fc_buf);

		/*
		 * 如果缓冲区是脏的，尝试将其写出。这里存在竞争条件，因为在我们上面释放它后，其他人可能会将其标记为脏，或者甚至在我们写出时（因为我们的共享锁不会阻止提示位更新）。我们将在重新锁定缓冲区头部后重新检查脏位。
		 */
		if (fc_oldFlags & BM_DIRTY)
		{
			/*
			 * 我们需要对缓冲区内容进行共享锁，以便写出它（否则我们可能会写入无效数据，例如因为其他人正在压缩页面内容）。在这里，我们必须使用条件锁获取以避免死锁。即使在 StrategyGetBuffer 返回时缓冲区未被固定（因此肯定未被锁定），当我们来到这里时，其他人可能已经固定并独占锁定了它。如果我们无条件尝试获取锁，我们会被阻塞以等待他们；如果他们后来被阻塞以等待我们，就会发生死锁。（这在两个后端都试图拆分 btree 索引页面时观察到，第二个后端恰好尝试拆分第一个从 StrategyGetBuffer 获取的页面。）
			 */
			if (LWLockConditionalAcquire(BufferDescriptorGetContentLock(fc_buf),
										 LW_SHARED))
			{
				/*
				 * 如果使用非默认策略，并且写入缓冲区将需要 WAL 刷新，允许策略决定是继续写入/重用缓冲区还是选择另一个受害者。我们需要锁来检查页面 LSN，因此无法在 StrategyGetBuffer 内完成此操作。
				 */
				if (fc_strategy != NULL)
				{
					XLogRecPtr	fc_lsn;

					/* 在持有缓冲区头部锁时读取 LSN */
					fc_buf_state = LockBufHdr(fc_buf);
					fc_lsn = BufferGetLSN(fc_buf);
					UnlockBufHdr(fc_buf, fc_buf_state);

					if (XLogNeedsFlush(fc_lsn) &&
						StrategyRejectBuffer(fc_strategy, fc_buf))
					{
						/* 释放锁/固定并循环获取另一个缓冲区 */
						LWLockRelease(BufferDescriptorGetContentLock(fc_buf));
						fc_UnpinBuffer(fc_buf, true);
						continue;
					}
				}

				/* 好的，进行 I/O */
				TRACE_POSTGRESQL_BUFFER_WRITE_DIRTY_START(forkNum, blockNum,
														  smgr->smgr_rnode.node.spcNode,
														  smgr->smgr_rnode.node.dbNode,
														  smgr->smgr_rnode.node.relNode);

				fc_FlushBuffer(fc_buf, NULL);
				LWLockRelease(BufferDescriptorGetContentLock(fc_buf));

				ScheduleBufferTagForWriteback(&BackendWritebackContext,
											  &fc_buf->tag);

				TRACE_POSTGRESQL_BUFFER_WRITE_DIRTY_DONE(forkNum, blockNum,
														 smgr->smgr_rnode.node.spcNode,
														 smgr->smgr_rnode.node.dbNode,
														 smgr->smgr_rnode.node.relNode);
			}
			else
			{
				/*
				 * 其他人已锁定缓冲区，因此放弃它并循环回去获取另一个。
				 */
				fc_UnpinBuffer(fc_buf, true);
				continue;
			}
		}

		/*
		 * 要更改有效缓冲区的关联，我们需要在旧的和新的映射分区上都拥有独占锁。
		 */
		if (fc_oldFlags & BM_TAG_VALID)
		{
			/*
			 * 需要计算旧标签的哈希码和分区锁 ID。XXX 存储哈希码在 BufferDesc 中是否值得，以便我们不需要在这里重新计算？可能不值得。
			 */
			fc_oldTag = fc_buf->tag;
			fc_oldHash = BufTableHashCode(&fc_oldTag);
			fc_oldPartitionLock = BufMappingPartitionLock(fc_oldHash);

			/*
			 * 必须首先锁定编号较低的分区以避免死锁。
			 */
			if (fc_oldPartitionLock < fc_newPartitionLock)
			{
				LWLockAcquire(fc_oldPartitionLock, LW_EXCLUSIVE);
				LWLockAcquire(fc_newPartitionLock, LW_EXCLUSIVE);
			}
			else if (fc_oldPartitionLock > fc_newPartitionLock)
			{
				LWLockAcquire(fc_newPartitionLock, LW_EXCLUSIVE);
				LWLockAcquire(fc_oldPartitionLock, LW_EXCLUSIVE);
			}
			else
			{
				/* 仅一个分区，仅一个锁 */
				LWLockAcquire(fc_newPartitionLock, LW_EXCLUSIVE);
			}
		}
		else
		{
			/* 如果它不是有效的，我们只需要新的分区 */
			LWLockAcquire(fc_newPartitionLock, LW_EXCLUSIVE);
			/* 记住我们没有旧分区锁或标签 */
			fc_oldPartitionLock = NULL;
			/* 让编译器安静下来，避免未初始化变量 */
			fc_oldHash = 0;
		}

		/*
		 * 尝试为缓冲区在其新标签下创建一个哈希表条目。这可能会失败，因为在我们写入时，其他人分配了另一个缓冲区以用于我们想要读取的相同块。请注意，我们尚未删除旧标签的哈希表条目。
		 */
		fc_buf_id = BufTableInsert(&fc_newTag, fc_newHash, fc_buf->buf_id);

		if (fc_buf_id >= 0)
		{
			/*
			 * 发生了冲突。有人已经完成了我们即将做的事情。我们将把此情况视为最初在缓冲池中找到的情况。首先，放弃我们原本打算使用的缓冲区。
			 */
			fc_UnpinBuffer(fc_buf, true);

			/* 现在可以放弃该缓冲区的映射分区锁 */
			if (fc_oldPartitionLock != NULL &&
				fc_oldPartitionLock != fc_newPartitionLock)
				LWLockRelease(fc_oldPartitionLock);

			/* 剩余代码应与例程顶部的代码匹配 */

			fc_buf = GetBufferDescriptor(fc_buf_id);

			fc_valid = fc_PinBuffer(fc_buf, fc_strategy);

			/* 一旦我们固定了它，就可以释放映射锁 */
			LWLockRelease(fc_newPartitionLock);

			*fc_foundPtr = true;

			if (!fc_valid)
			{
				/*
				 * 我们只能在 (a) 还有其他人正在读取该页面，或者 (b) 之前的读取尝试失败时来到这里。我们必须等待所有活动的读取尝试完成，然后在页面仍然不是 BM_VALID 时设置我们自己的读取尝试。StartBufferIO 完成了所有操作。
				 */
				if (fc_StartBufferIO(fc_buf, true))
				{
					/*
					 * 如果我们来到这里，之前读取缓冲区的尝试一定失败了……但我们将勇敢地再试一次。
					 */
					*fc_foundPtr = false;
				}
			}

			return fc_buf;
		}

		/*
		 * 还需要锁定缓冲区头以更改其标签。
		 */
		fc_buf_state = LockBufHdr(fc_buf);

		/*
		 * 在我们进行 I/O 和创建新的哈希表条目时，有人可能已经固定或重新标记了缓冲区。如果是这样，我们无法重用此缓冲区；必须撤销我们所做的一切，并从新的牺牲者缓冲区重新开始。
		 */
		fc_oldFlags = fc_buf_state & BUF_FLAG_MASK;
		if (BUF_STATE_GET_REFCOUNT(fc_buf_state) == 1 && !(fc_oldFlags & BM_DIRTY))
			break;

		UnlockBufHdr(fc_buf, fc_buf_state);
		BufTableDelete(&fc_newTag, fc_newHash);
		if (fc_oldPartitionLock != NULL &&
			fc_oldPartitionLock != fc_newPartitionLock)
			LWLockRelease(fc_oldPartitionLock);
		LWLockRelease(fc_newPartitionLock);
		fc_UnpinBuffer(fc_buf, true);
	}

	/*
	 * 好吧，现在终于可以安全地重命名缓冲区了。
	 *
	 * 在这里清除 BM_VALID 是必要的，清除 dirtybits 只是出于偏执。我们还重置 usage_count，因为旧内容的任何使用时间不再相关。（usage_count 最初设置为 1，以便缓冲区可以经受一次时钟轮扫的过程。）
	 *
	 * 确保对需要在每个检查点写入的缓冲区设置 BM_PERMANENT。未记录的缓冲区只需在关闭检查点时写入，除了它们的“init”分叉，它们需要像永久关系一样处理。
	 */
	fc_buf->tag = fc_newTag;
	fc_buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED |
				   BM_CHECKPOINT_NEEDED | BM_IO_ERROR | BM_PERMANENT |
				   BUF_USAGECOUNT_MASK);
	if (fc_relpersistence == RELPERSISTENCE_PERMANENT || fc_forkNum == INIT_FORKNUM)
		fc_buf_state |= BM_TAG_VALID | BM_PERMANENT | BUF_USAGECOUNT_ONE;
	else
		fc_buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;

	UnlockBufHdr(fc_buf, fc_buf_state);

	if (fc_oldPartitionLock != NULL)
	{
		BufTableDelete(&fc_oldTag, fc_oldHash);
		if (fc_oldPartitionLock != fc_newPartitionLock)
			LWLockRelease(fc_oldPartitionLock);
	}

	LWLockRelease(fc_newPartitionLock);

	/*
	 * 缓冲区内容当前无效。尝试获得开始 I/O 的权利。如果 StartBufferIO 返回 false，那么在我们之前，有其他人成功读取了它，因此 BufferAlloc() 没有什么可做的。
	 */
	if (fc_StartBufferIO(fc_buf, true))
		*fc_foundPtr = false;
	else
		*fc_foundPtr = true;

	return fc_buf;
}

/*
 * InvalidateBuffer -- 标记一个共享缓冲区为无效，并将其返回到空闲列表中。
 *
 * 在进入时必须保持缓冲区头的自旋锁。我们在返回之前放下它。（这是合理的，因为调用者必须已经锁定了缓冲区，以确保它应该被释放。）
 *
 * 这仅在诸如删除关系等上下文中使用。我们假设没有其他后台可能对使用该页面感兴趣，因此缓冲区被固定的唯一原因是其他人试图将其写出。在我们重新获取缓冲区之前，必须让他们完成。
 *
 * 在我们等待获取必要的锁时，缓冲区可能会被其他人回收；如果是这样，不要搞砸它。
 */
static void fc_InvalidateBuffer(BufferDesc *fc_buf)
{
	BufferTag	fc_oldTag;
	uint32		fc_oldHash;		/* oldTag 的哈希值 */
	LWLock	   *fc_oldPartitionLock;	/* 其缓冲区分区锁 */
	uint32		fc_oldFlags;
	uint32		fc_buf_state;

	/* 在释放自旋锁之前保存原始缓冲区标签 */
	fc_oldTag = fc_buf->tag;

	fc_buf_state = pg_atomic_read_u32(&fc_buf->state);
	Assert(fc_buf_state & BM_LOCKED);
	UnlockBufHdr(fc_buf, fc_buf_state);

	/*
	 * 需要计算旧标签的哈希码和分区锁 ID。XXX 是否值得将哈希码存储在 BufferDesc 中，以便我们不需要在这里重新计算它？可能不值得。
	 */
	fc_oldHash = BufTableHashCode(&fc_oldTag);
	fc_oldPartitionLock = BufMappingPartitionLock(fc_oldHash);

retry:

	/*
	 * 获取独占映射锁，以准备更改缓冲区的关联。
	 */
	LWLockAcquire(fc_oldPartitionLock, LW_EXCLUSIVE);

	/* 重新锁定缓冲区头 */
	fc_buf_state = LockBufHdr(fc_buf);

	/* 如果在我们等待锁时发生更改，则不进行任何操作 */
	if (!BUFFERTAGS_EQUAL(fc_buf->tag, fc_oldTag))
	{
		UnlockBufHdr(fc_buf, fc_buf_state);
		LWLockRelease(fc_oldPartitionLock);
		return;
	}

	/*
	 * 我们假设它被固定的唯一原因是其他人正在刷新页面。等他们完成。（如果引用计数出现问题，这可能会是一个无限循环……最好在一段时间后超时，但似乎无法确保需要多少次循环。请注意，如果其他人已经固定了缓冲区但还没有执行 StartBufferIO，WaitIO 将会失败，我们将在这里有效地忙于循环。）
	 */
	if (BUF_STATE_GET_REFCOUNT(fc_buf_state) != 0)
	{
		UnlockBufHdr(fc_buf, fc_buf_state);
		LWLockRelease(fc_oldPartitionLock);
		/* 安全检查：绝对不应该是我们自己的固定 */
		if (fc_GetPrivateRefCount(BufferDescriptorGetBuffer(fc_buf)) > 0)
			elog(ERROR, "buffer is pinned in InvalidateBuffer");
		fc_WaitIO(fc_buf);
		goto retry;
	}

	/*
	 * 清除缓冲区的标签和标志。我们必须这样做，以确保对缓冲区数组的线性扫描不会认为缓冲区是有效的。
	 */
	fc_oldFlags = fc_buf_state & BUF_FLAG_MASK;
	CLEAR_BUFFERTAG(fc_buf->tag);
	fc_buf_state &= ~(BUF_FLAG_MASK | BUF_USAGECOUNT_MASK);
	UnlockBufHdr(fc_buf, fc_buf_state);

	/*
	 * 如果缓冲区在查找哈希表中，则将其从中移除。
	 */
	if (fc_oldFlags & BM_TAG_VALID)
		BufTableDelete(&fc_oldTag, fc_oldHash);

	/*
	 * 完成映射锁。
	 */
	LWLockRelease(fc_oldPartitionLock);

	/*
	 * 将缓冲区插入到空闲缓冲区列表的头部。
	 */
	StrategyFreeBuffer(fc_buf);
}

/*
 * MarkBufferDirty
 *
 *		将缓冲区内容标记为脏（实际写入发生在后面）。
 *
 * 缓冲区必须被固定并独占锁定。（如果调用者没有持有独占锁，则可能有其他正在写入该缓冲区的人，从而导致写入到磁盘的数据有风险。）
 */
void MarkBufferDirty(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;
	uint32		fc_buf_state;
	uint32		fc_old_buf_state;

	if (!BufferIsValid(fc_buffer))
		elog(ERROR, "bad buffer ID: %d", fc_buffer);

	if (BufferIsLocal(fc_buffer))
	{
		MarkLocalBufferDirty(fc_buffer);
		return;
	}

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	Assert(BufferIsPinned(fc_buffer));
	Assert(LWLockHeldByMeInMode(BufferDescriptorGetContentLock(fc_bufHdr),
								LW_EXCLUSIVE));

	fc_old_buf_state = pg_atomic_read_u32(&fc_bufHdr->state);
	for (;;)
	{
		if (fc_old_buf_state & BM_LOCKED)
			fc_old_buf_state = fc_WaitBufHdrUnlocked(fc_bufHdr);

		fc_buf_state = fc_old_buf_state;

		Assert(BUF_STATE_GET_REFCOUNT(fc_buf_state) > 0);
		fc_buf_state |= BM_DIRTY | BM_JUST_DIRTIED;

		if (pg_atomic_compare_exchange_u32(&fc_bufHdr->state, &fc_old_buf_state,
										   fc_buf_state))
			break;
	}

	/*
	 * 如果缓冲区之前不脏，则进行清理核算。
	 */
	if (!(fc_old_buf_state & BM_DIRTY))
	{
		VacuumPageDirty++;
		pgBufferUsage.shared_blks_dirtied++;
		if (VacuumCostActive)
			VacuumCostBalance += VacuumCostPageDirty;
	}
}

/*
 * ReleaseAndReadBuffer -- 结合 ReleaseBuffer() 和 ReadBuffer()
 *
 * 以前，这比单独调用这两个例程节省了一个获取/释放 BufMgrLock 的周期。现在主要只是一个便利功能。然而，如果传递的缓冲区有效并且已包含所需的块，我们只需按原样返回它；而这相比于完全释放和重新获取确实节省了大量工作。
 *
 * 注意：传递 buffer == InvalidBuffer 是可以的，这表示实际上不需要释放任何旧缓冲区。该情况与 ReadBuffer 相同，但可以节省调用者的一些测试。
 */
Buffer ReleaseAndReadBuffer(Buffer fc_buffer,
					 Relation fc_relation,
					 BlockNumber fc_blockNum)
{
	ForkNumber	fc_forkNum = MAIN_FORKNUM;
	BufferDesc *fc_bufHdr;

	if (BufferIsValid(fc_buffer))
	{
		Assert(BufferIsPinned(fc_buffer));
		if (BufferIsLocal(fc_buffer))
		{
			fc_bufHdr = GetLocalBufferDescriptor(-fc_buffer - 1);
			if (fc_bufHdr->tag.blockNum == fc_blockNum &&
				RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_relation->rd_node) &&
				fc_bufHdr->tag.forkNum == fc_forkNum)
				return fc_buffer;
			ResourceOwnerForgetBuffer(CurrentResourceOwner, fc_buffer);
			LocalRefCount[-fc_buffer - 1]--;
		}
		else
		{
			fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);
			/* 我们有固定，因此可以在没有自旋锁的情况下检查标签 */
			if (fc_bufHdr->tag.blockNum == fc_blockNum &&
				RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_relation->rd_node) &&
				fc_bufHdr->tag.forkNum == fc_forkNum)
				return fc_buffer;
			fc_UnpinBuffer(fc_bufHdr, true);
		}
	}

	return ReadBuffer(fc_relation, fc_blockNum);
}


/*
 * PinBuffer -- 使缓冲区无法被替换。
 *
 * 对于默认的访问策略，当我们第一次固定它时，缓冲区的 usage_count 会增加；
 * 对于其他策略，我们只需确保 usage_count 不为零。 （后者的想法是，
 * 我们不希望同步堆扫描膨胀计数，但我们需要它不为零，以防止其他后端从我们的环中窃取缓冲区。
 * 只要我们遍历环的速度快于全局时钟扫频，环中的缓冲区就不会被其他后端选为替换的牺牲品。）
 *
 * 这只应应用于共享缓冲区，绝不应用于本地缓冲区。
 *
 * 由于缓冲区的固定/释放非常频繁，因此在不获取缓冲区头锁的情况下固定缓冲区；
 * 而是在线程的 CAS 操作循环中更新状态变量。希望这只是一个简单的 CAS。
 *
 * 请注意，ResourceOwnerEnlargeBuffers 必须已经完成。
 *
 * 如果缓冲区是 BM_VALID，则返回 true，否则返回 false。此条款允许某些调用者避免额外的自旋锁周期。
 */
static bool fc_PinBuffer(BufferDesc *fc_buf, BufferAccessStrategy fc_strategy)
{
	Buffer		fc_b = BufferDescriptorGetBuffer(fc_buf);
	bool		fc_result;
	PrivateRefCountEntry *fc_ref;

	fc_ref = fc_GetPrivateRefCountEntry(fc_b, true);

	if (fc_ref == NULL)
	{
		uint32		fc_buf_state;
		uint32		fc_old_buf_state;

		fc_ReservePrivateRefCountEntry();
		fc_ref = fc_NewPrivateRefCountEntry(fc_b);

		fc_old_buf_state = pg_atomic_read_u32(&fc_buf->state);
		for (;;)
		{
			if (fc_old_buf_state & BM_LOCKED)
				fc_old_buf_state = fc_WaitBufHdrUnlocked(fc_buf);

			fc_buf_state = fc_old_buf_state;

			/* 增加引用计数 */
			fc_buf_state += BUF_REFCOUNT_ONE;

			if (fc_strategy == NULL)
			{
				/* 默认情况：增加 usagecount 除非已经是最大值。 */
				if (BUF_STATE_GET_USAGECOUNT(fc_buf_state) < BM_MAX_USAGE_COUNT)
					fc_buf_state += BUF_USAGECOUNT_ONE;
			}
			else
			{
				/*
				 * 环形缓冲区不应该逐出池中其他缓冲区。因此，我们
				 * 不会将 usagecount 增加到超过 1。
				 */
				if (BUF_STATE_GET_USAGECOUNT(fc_buf_state) == 0)
					fc_buf_state += BUF_USAGECOUNT_ONE;
			}

			if (pg_atomic_compare_exchange_u32(&fc_buf->state, &fc_old_buf_state,
											   fc_buf_state))
			{
				fc_result = (fc_buf_state & BM_VALID) != 0;

				/*
				 * 假设我们出于 Valgrind 缓冲区客户端检查的目的获取了一个缓冲区的固定（即使在 !result 的情况下），
				 * 以保持简单。无法安全访问的缓冲区并不一定会被标记为未定义或
				 * 在任何情况下都是不可访问的。
				 */
				VALGRIND_MAKE_MEM_DEFINED(BufHdrGetBlock(buf), BLCKSZ);
				break;
			}
		}
	}
	else
	{
		/*
		 * 如果我们之前固定了缓冲区，它一定是有效的。
		 *
		 * 注意：我们故意在这里避免 Valgrind 客户端请求。
		 * 各种访问方法可以选择性地在我们的客户端请求之上叠加缓冲区页面
		 * 客户端请求，以强制在锁定（和固定）时仅访问缓冲区。
		 * 这里的缓冲区页面可能合法地不可访问。我们无法干涉这一点。
		 */
		fc_result = true;
	}

	fc_ref->refcount++;
	Assert(fc_ref->refcount > 0);
	ResourceOwnerRememberBuffer(CurrentResourceOwner, fc_b);
	return fc_result;
}

/*
 * PinBuffer_Locked -- 如上所述，但调用者已经锁定了缓冲区头。
 * 在返回之前释放自旋锁。
 *
 * 由于此函数在持有自旋锁的状态下被调用，调用者必须先调用 ReservePrivateRefCountEntry()。
 *
 * 目前，这个函数的调用者都不想修改缓冲区的 usage_count，因此没有必要传递策略参数。
 * 我们也不必进行 BM_VALID 测试（调用者可以自行检查）。
 *
 * 此外，所有调用者只在已知此缓冲区不能被该后端预先固定时使用此函数。
 * 这使我们可以跳过搜索私有引用计数数组和哈希表，这是一个好处，因为自旋锁仍然被保持。
 *
 * 注意：使用此例程通常是强制性的，而不只是为了节省自旋锁/解锁周期，因为我们需要在状态发生变化之前固定缓冲区。
 */
static void fc_PinBuffer_Locked(BufferDesc *fc_buf)
{
	Buffer		fc_b;
	PrivateRefCountEntry *fc_ref;
	uint32		fc_buf_state;

	/*
	 * 如上所述，我们不期望任何预先存在的固定。这样我们就可以在释放自旋锁之后
	 * 操作 PrivateRefCount。
	 */
	Assert(fc_GetPrivateRefCountEntry(BufferDescriptorGetBuffer(fc_buf), false) == NULL);

	/*
	 * 缓冲区不能有预先存在的固定，因此将其页面标记为对 Valgrind 已定义
	 * （这类似于 PinBuffer() 的情况，其中后端尚未具有缓冲区固定）
	 */
	VALGRIND_MAKE_MEM_DEFINED(BufHdrGetBlock(buf), BLCKSZ);

	/*
	 * 由于我们持有缓冲区自旋锁，因此可以在一个操作中更新缓冲区状态并释放锁。
	 */
	fc_buf_state = pg_atomic_read_u32(&fc_buf->state);
	Assert(fc_buf_state & BM_LOCKED);
	fc_buf_state += BUF_REFCOUNT_ONE;
	UnlockBufHdr(fc_buf, fc_buf_state);

	fc_b = BufferDescriptorGetBuffer(fc_buf);

	fc_ref = fc_NewPrivateRefCountEntry(fc_b);
	fc_ref->refcount++;

	ResourceOwnerRememberBuffer(CurrentResourceOwner, fc_b);
}

/*
 * UnpinBuffer -- 使缓冲区可以被替换。
 *
 * 这只应该应用于共享缓冲区，而不是本地缓冲区。
 *
 * 大多数但并非所有调用者希望调整 CurrentResourceOwner。
 * 那些不希望调整的应传递 fixOwner = false。
 */
static void fc_UnpinBuffer(BufferDesc *fc_buf, bool fc_fixOwner)
{
	PrivateRefCountEntry *fc_ref;
	Buffer		fc_b = BufferDescriptorGetBuffer(fc_buf);

	/* 由于我们可能很快就会删除它，因此不移动 */
	fc_ref = fc_GetPrivateRefCountEntry(fc_b, false);
	Assert(fc_ref != NULL);

	if (fc_fixOwner)
		ResourceOwnerForgetBuffer(CurrentResourceOwner, fc_b);

	Assert(fc_ref->refcount > 0);
	fc_ref->refcount--;
	if (fc_ref->refcount == 0)
	{
		uint32		fc_buf_state;
		uint32		fc_old_buf_state;

		/*
		 * 将缓冲区标记为对 Valgrind 不可访问。
		 *
		 * 注意，缓冲区可能已经在访问方法代码中被标记为不可访问，
		 * 该代码强制要求缓冲区在持有缓冲区锁时才被访问。
		 */
		VALGRIND_MAKE_MEM_NOACCESS(BufHdrGetBlock(buf), BLCKSZ);

		/* 我最好不要仍然持有缓冲区内容锁 */
		Assert(!LWLockHeldByMe(BufferDescriptorGetContentLock(fc_buf)));

		/*
		 * 减少共享引用计数。
		 *
		 * 由于缓冲区自旋锁持有者可以仅通过写入更新状态，
		 * 因此在这里使用原子递减是不安全的；因此使用 CAS 循环。
		 */
		fc_old_buf_state = pg_atomic_read_u32(&fc_buf->state);
		for (;;)
		{
			if (fc_old_buf_state & BM_LOCKED)
				fc_old_buf_state = fc_WaitBufHdrUnlocked(fc_buf);

			fc_buf_state = fc_old_buf_state;

			fc_buf_state -= BUF_REFCOUNT_ONE;

			if (pg_atomic_compare_exchange_u32(&fc_buf->state, &fc_old_buf_state,
											   fc_buf_state))
				break;
		}

		/* 支持 LockBufferForCleanup() */
		if (fc_buf_state & BM_PIN_COUNT_WAITER)
		{
			/*
			 * 获取缓冲区头锁，重新检查是否有等待者。
			 * 其他后端可能已经取消固定此缓冲区，并已
			 * 唤醒等待者。我们在上面取消固定后，缓冲区被
			 * 等待者固定，因此不会有被替换的危险。
			 */
			fc_buf_state = LockBufHdr(fc_buf);

			if ((fc_buf_state & BM_PIN_COUNT_WAITER) &&
				BUF_STATE_GET_REFCOUNT(fc_buf_state) == 1)
			{
				/* 我们刚刚释放了除了等待者之外的最后一个固定 */
				int			fc_wait_backend_pgprocno = fc_buf->wait_backend_pgprocno;

				fc_buf_state &= ~BM_PIN_COUNT_WAITER;
				UnlockBufHdr(fc_buf, fc_buf_state);
				ProcSendSignal(fc_wait_backend_pgprocno);
			}
			else
				UnlockBufHdr(fc_buf, fc_buf_state);
		}
		fc_ForgetPrivateRefCountEntry(fc_ref);
	}
}

#define ST_SORT sort_checkpoint_bufferids
#define ST_ELEMENT_TYPE CkptSortItem
#define ST_COMPARE(a, b) fc_ckpt_buforder_comparator(a, b)
#define ST_SCOPE static
#define ST_DEFINE
#include <lib/sort_template.h>

/*
 * BufferSync -- 将池中的所有脏缓冲区写出。
 *
 * 在检查点时间调用此函数以写出所有脏的共享缓冲区。
 * 检查点请求标志应被传递。如果设置了 CHECKPOINT_IMMEDIATE，
 * 我们就禁用写入之间的延迟；如果设置了 CHECKPOINT_IS_SHUTDOWN,
 * CHECKPOINT_END_OF_RECOVERY 或 CHECKPOINT_FLUSH_ALL，我们甚至会
 * 写入未记录的缓冲区，后者通常会被跳过。剩余的标志
 * 当前在这里没有效果。
 */
static void fc_BufferSync(int fc_flags)
{
	uint32		fc_buf_state;
	int			fc_buf_id;
	int			fc_num_to_scan;
	int			fc_num_spaces;
	int			fc_num_processed;
	int			fc_num_written;
	CkptTsStatus *fc_per_ts_stat = NULL;
	Oid			fc_last_tsid;
	binaryheap *fc_ts_heap;
	int			fc_i;
	int			fc_mask = BM_DIRTY;
	WritebackContext fc_wb_context;

	/* 确保我们可以在 SyncOneBuffer 内部处理固定 */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	/*
	 * 除非这是一个关闭检查点或我们被明确告知，
	 * 否则我们只写入永久的、脏的缓冲区。但在关闭或恢复结束时，
	 * 我们写入所有脏的缓冲区。
	 */
	if (!((fc_flags & (CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_END_OF_RECOVERY |
					CHECKPOINT_FLUSH_ALL))))
		fc_mask |= BM_PERMANENT;

	/*
	 * 循环遍历所有缓冲区，并用 BM_CHECKPOINT_NEEDED 标记
	 * 需要写入的缓冲区。在此过程中计数 (num_to_scan)，
	 * 以便我们可以估算需要完成的工作量。
	 *
	 * 这使我们能够仅写入在检查点开始时脏的页面，而不写入在
	 * 期间变脏的页面。每当带有 BM_CHECKPOINT_NEEDED 的页面被
	 * 写出时，无论是我们稍后在此函数中、正常后端或 bgwriter
	 * 清理扫描，它的标志都会被清除。此时之后变脏的任何缓冲区
	 * 都不会设置该标志。
	 *
	 * 请注意，如果我们未能写出某些缓冲区，可能会留下仍然
	 * 设置 BM_CHECKPOINT_NEEDED 的缓冲区。这是可以的，
	 * 因为任何这样的缓冲区在下次检查点尝试时肯定需要被写入。
	 */
	fc_num_to_scan = 0;
	for (fc_buf_id = 0; fc_buf_id < NBuffers; fc_buf_id++)
	{
		BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_buf_id);

		/*
		 * 头锁足以检查 BM_DIRTY，详见
		 * SyncOneBuffer 中的注释。
		 */
		fc_buf_state = LockBufHdr(fc_bufHdr);

		if ((fc_buf_state & fc_mask) == fc_mask)
		{
			CkptSortItem *fc_item;

			fc_buf_state |= BM_CHECKPOINT_NEEDED;

			fc_item = &CkptBufferIds[fc_num_to_scan++];
			fc_item->buf_id = fc_buf_id;
			fc_item->tsId = fc_bufHdr->tag.rnode.spcNode;
			fc_item->relNode = fc_bufHdr->tag.rnode.relNode;
			fc_item->forkNum = fc_bufHdr->tag.forkNum;
			fc_item->blockNum = fc_bufHdr->tag.blockNum;
		}

		UnlockBufHdr(fc_bufHdr, fc_buf_state);

		/* 检查 NBuffers 较大时的障碍事件。 */
		if (ProcSignalBarrierPending)
			ProcessProcSignalBarrier();
	}

	if (fc_num_to_scan == 0)
		return;					/* 无需操作 */

	WritebackContextInit(&fc_wb_context, &checkpoint_flush_after);

	TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, fc_num_to_scan);

	/*
	 * 对需要写入的缓冲区进行排序，以减少随机 IO 的可能性。
	 * 排序对于在表空间之间平衡写入的实现也很重要。
	 * 如果不平衡写入，我们可能会逐个表空间写入；可能会过载
	 * 底层系统。
	 */
	sort_checkpoint_bufferids(CkptBufferIds, fc_num_to_scan);

	fc_num_spaces = 0;

	/*
	 * 为每个需要刷新缓冲区的表空间分配进度状态。
	 * 这需要待刷新的数组是已排序的。
	 */
	fc_last_tsid = InvalidOid;
	for (fc_i = 0; fc_i < fc_num_to_scan; fc_i++)
	{
		CkptTsStatus *fc_s;
		Oid			fc_cur_tsid;

		fc_cur_tsid = CkptBufferIds[fc_i].tsId;

		/*
		 * 每次找到新的表空间时，增加每个表空间状态结构的数组。
		 */
		if (fc_last_tsid == InvalidOid || fc_last_tsid != fc_cur_tsid)
		{
			Size		fc_sz;

			fc_num_spaces++;

			/*
			 * 在这里添加按 2 的幂增长的逻辑不值得 - 即使有几百个表空间
			 * 这应该也没问题。
			 */
			fc_sz = sizeof(CkptTsStatus) * fc_num_spaces;

			if (fc_per_ts_stat == NULL)
				fc_per_ts_stat = (CkptTsStatus *) palloc(fc_sz);
			else
				fc_per_ts_stat = (CkptTsStatus *) repalloc(fc_per_ts_stat, fc_sz);

			fc_s = &fc_per_ts_stat[fc_num_spaces - 1];
			memset(fc_s, 0, sizeof(*fc_s));
			fc_s->tsId = fc_cur_tsid;

			/*
			 * 此表空间中的第一个缓冲区。由于 CkptBufferIds 是按表空间
			 * 排序的，因此此表空间中的所有 (s->num_to_scan) 缓冲区
			 * 将随之而来。
			 */
			fc_s->index = fc_i;

			/*
			 * progress_slice 将在我们知道每个表空间中有多少缓冲区
			 * 后确定，即在此循环之后。
			 */

			fc_last_tsid = fc_cur_tsid;
		}
		else
		{
			fc_s = &fc_per_ts_stat[fc_num_spaces - 1];
		}

		fc_s->num_to_scan++;

		/* 检查障碍事件。 */
		if (ProcSignalBarrierPending)
			ProcessProcSignalBarrier();
	}

	Assert(fc_num_spaces > 0);

	/*
	 * 在各个表空间上构建一个最小堆来跟踪写入进度，并计算单个
	 * 处理缓冲区占总进度的多大部分。
	 */
	fc_ts_heap = binaryheap_allocate(fc_num_spaces,
								  fc_ts_ckpt_progress_comparator,
								  NULL);

	for (fc_i = 0; fc_i < fc_num_spaces; fc_i++)
	{
		CkptTsStatus *fc_ts_stat = &fc_per_ts_stat[fc_i];

		fc_ts_stat->progress_slice = (float8) fc_num_to_scan / fc_ts_stat->num_to_scan;

		binaryheap_add_unordered(fc_ts_heap, PointerGetDatum(fc_ts_stat));
	}

	binaryheap_build(fc_ts_heap);

	/*
	 * 遍历待检查点的缓冲区，并写入标记为 BM_CHECKPOINT_NEEDED
	 * 的缓冲区（仍然）。写入在表空间之间进行平衡；否则排序
	 * 将导致一次只对一个表空间进行写入，导致对硬件使用不
	 * 高效。
	 */
	fc_num_processed = 0;
	fc_num_written = 0;
	while (!binaryheap_empty(fc_ts_heap))
	{
		BufferDesc *fc_bufHdr = NULL;
		CkptTsStatus *fc_ts_stat = (CkptTsStatus *)
		DatumGetPointer(binaryheap_first(fc_ts_heap));

		fc_buf_id = CkptBufferIds[fc_ts_stat->index].buf_id;
		Assert(fc_buf_id != -1);

		fc_bufHdr = GetBufferDescriptor(fc_buf_id);

		fc_num_processed++;

		
/*
		 * 我们在这里不需要获取锁，因为我们只是在查看
		 * 一个单独的位。可能在我们检查之后还有其他人写入缓冲区
		 * 并清除标志，但这无关紧要，因为 SyncOneBuffer 随后将不执行任何操作。
		 * 然而，还有一个进一步的竞争条件：可以想象，在我们
		 * 在这里检查位和 SyncOneBuffer 获取锁之间，
		 * 其他人不仅写入了缓冲区，还用另一个页面替换了它并弄脏了它。
		 * 在这种不太可能的情况下，SyncOneBuffer 将
		 * 写入缓冲区，尽管我们并不需要这样做。不过，这似乎不值得
		 * 进行防范。
		 */
		if (pg_atomic_read_u32(&fc_bufHdr->state) & BM_CHECKPOINT_NEEDED)
		{
			if (fc_SyncOneBuffer(fc_buf_id, false, &fc_wb_context) & BUF_WRITTEN)
			{
				TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
				PendingCheckpointerStats.buf_written_checkpoints++;
				fc_num_written++;
			}
		}

		/*
		 * 测量进度而不需要实际刷新缓冲区
		 * - 否则写入将变得不平衡。
		 */
		fc_ts_stat->progress += fc_ts_stat->progress_slice;
		fc_ts_stat->num_scanned++;
		fc_ts_stat->index++;

		/* 所有来自表空间的缓冲区都处理完了吗？ */
		if (fc_ts_stat->num_scanned == fc_ts_stat->num_to_scan)
		{
			binaryheap_remove_first(fc_ts_heap);
		}
		else
		{
			/* 用新进度更新堆 */
			binaryheap_replace_first(fc_ts_heap, PointerGetDatum(fc_ts_stat));
		}

		/*
		 * 睡眠以限制我们的 I/O 速率。
		 *
		 * （这将检查障碍事件，即使它不睡眠。）
		 */
		CheckpointWriteDelay(fc_flags, (double) fc_num_processed / fc_num_to_scan);
	}

	/* 发起所有待处理的刷新 */
	IssuePendingWritebacks(&fc_wb_context);

	pfree(fc_per_ts_stat);
	fc_per_ts_stat = NULL;
	binaryheap_free(fc_ts_heap);

	/*
	 * 更新检查点统计信息。如上所述，这不包括
	 * 由其他后端或 bgwriter 扫描写入的缓冲区。
	 */
	CheckpointStats.ckpt_bufs_written += fc_num_written;

	TRACE_POSTGRESQL_BUFFER_SYNC_DONE(NBuffers, fc_num_written, fc_num_to_scan);
}

/*
 * BgBufferSync -- 将一些脏缓冲区写出到池中。
 *
 * 这是由后台写入进程定期调用的。
 *
 * 如果适合后台写入进程进入
 * 低功耗休眠模式，则返回 true。（如果策略时钟扫过
 * 被“超越”且最近没有缓冲区分配，
 * 或者如果由于将 bgwriter_lru_maxpages 设置为 0，
 * bgwriter 实际上被禁用。）
 */
bool BgBufferSync(WritebackContext *fc_wb_context)
{
	/* 从freelist.c获得的信息 */
	int			fc_strategy_buf_id;
	uint32		fc_strategy_passes;
	uint32		fc_recent_alloc;

	/*
	 * 在调用之间保存的信息，以便我们能够确定策略
	 * 点的前进速度并避免扫描已清理的缓冲区。
	 */
	static bool fc_saved_info_valid = false;
	static int	fc_prev_strategy_buf_id;
	static uint32 fc_prev_strategy_passes;
	static int	fc_next_to_clean;
	static uint32 fc_next_passes;

	/* 分配速率和清洁缓冲区密度的移动平均值 */
	static float fc_smoothed_alloc = 0;
	static float fc_smoothed_density = 10.0;

	/* 潜在地，这些可以是可调参数，但目前不是 */
	float		fc_smoothing_samples = 16;
	float		fc_scan_whole_pool_milliseconds = 120000.0;

	/* 用于计算我们扫描的前进距离 */
	long		fc_strategy_delta;
	int			fc_bufs_to_lap;
	int			fc_bufs_ahead;
	float		fc_scans_per_alloc;
	int			fc_reusable_buffers_est;
	int			fc_upcoming_alloc_est;
	int			fc_min_scan_buffers;

	/* 扫描循环本身的变量 */
	int			fc_num_to_scan;
	int			fc_num_written;
	int			fc_reusable_buffers;

	/* 最终平滑密度更新的变量 */
	long		fc_new_strategy_delta;
	uint32		fc_new_recent_alloc;

	/*
	 * 查明自由列表时钟扫描当前的位置，以及自上次调用以来
	 * 发生了多少次缓冲区分配。
	 */
	fc_strategy_buf_id = StrategySyncStart(&fc_strategy_passes, &fc_recent_alloc);

	/* 向pgstat报告缓冲区分配计数 */
	PendingBgWriterStats.buf_alloc += fc_recent_alloc;

	/*
	 * 如果我们不在运行LRU扫描，仅在做完统计
	 * 工作后停止。我们将保存的状态标记为无效，以便在后续
	 * LRU扫描重新开启时可以合理地恢复。
	 */
	if (bgwriter_lru_maxpages <= 0)
	{
		fc_saved_info_valid = false;
		return true;
	}

	/*
	 * 计算strategy_delta = 自上次以来时钟扫描已扫描的
	 * 缓冲区数量。如果是第一次通过，假设没有。然后
	 * 检查我们是否仍然领先于时钟扫描，如果是的话，我们能在赶上
	 * 时钟扫描之前扫描多少个缓冲区并“超越”它。注意：
	 * weird-looking coding of xxx_passes comparisons是为了避免
	 * 当passes计数回绕时的虚假行为。
	 */
	if (fc_saved_info_valid)
	{
		int32		fc_passes_delta = fc_strategy_passes - fc_prev_strategy_passes;

		fc_strategy_delta = fc_strategy_buf_id - fc_prev_strategy_buf_id;
		fc_strategy_delta += (long) fc_passes_delta * NBuffers;

		Assert(fc_strategy_delta >= 0);

		if ((int32) (fc_next_passes - fc_strategy_passes) > 0)
		{
			/* 我们领先于策略点一个周期 */
			fc_bufs_to_lap = fc_strategy_buf_id - fc_next_to_clean;
#ifdef BGW_DEBUG
			elog(DEBUG2, "bgwriter ahead: bgw %u-%u strategy %u-%u delta=%ld lap=%d",
				 fc_next_passes, fc_next_to_clean,
				 fc_strategy_passes, fc_strategy_buf_id,
				 fc_strategy_delta, fc_bufs_to_lap);
#endif
		}
		else if (fc_next_passes == fc_strategy_passes &&
				 fc_next_to_clean >= fc_strategy_buf_id)
		{
			/* 在同一周期内，但领先或至少不落后 */
			fc_bufs_to_lap = NBuffers - (fc_next_to_clean - fc_strategy_buf_id);
#ifdef BGW_DEBUG
			elog(DEBUG2, "bgwriter ahead: bgw %u-%u strategy %u-%u delta=%ld lap=%d",
				 fc_next_passes, fc_next_to_clean,
				 fc_strategy_passes, fc_strategy_buf_id,
				 fc_strategy_delta, fc_bufs_to_lap);
#endif
		}
		else
		{
			/*
			 * 我们落后了，所以跳过到策略点并从那里开始
			 * 清理。
			 */
#ifdef BGW_DEBUG
			elog(DEBUG2, "bgwriter behind: bgw %u-%u strategy %u-%u delta=%ld",
				 fc_next_passes, fc_next_to_clean,
				 fc_strategy_passes, fc_strategy_buf_id,
				 fc_strategy_delta);
#endif
			fc_next_to_clean = fc_strategy_buf_id;
			fc_next_passes = fc_strategy_passes;
			fc_bufs_to_lap = NBuffers;
		}
	}
	else
	{
		/*
		 * 在启动时或在LRU扫描关闭后初始化。始终
		 * 从策略点开始。
		 */
#ifdef BGW_DEBUG
		elog(DEBUG2, "bgwriter initializing: strategy %u-%u",
			 fc_strategy_passes, fc_strategy_buf_id);
#endif
		fc_strategy_delta = 0;
		fc_next_to_clean = fc_strategy_buf_id;
		fc_next_passes = fc_strategy_passes;
		fc_bufs_to_lap = NBuffers;
	}

	/* 更新下次使用的保存信息 */
	fc_prev_strategy_buf_id = fc_strategy_buf_id;
	fc_prev_strategy_passes = fc_strategy_passes;
	fc_saved_info_valid = true;

	/*
	 * 计算每次新分配必须扫描的缓冲区数量，即，
	 * 1/可重用缓冲区的密度，并跟踪其移动平均值。
	 *
	 * 如果策略点没有移动，我们不更新密度估计
	 */
	if (fc_strategy_delta > 0 && fc_recent_alloc > 0)
	{
		fc_scans_per_alloc = (float) fc_strategy_delta / (float) fc_recent_alloc;
		fc_smoothed_density += (fc_scans_per_alloc - fc_smoothed_density) /
			fc_smoothing_samples;
	}

	/*
	 * 估计在当前
	 * 策略点与我们扫描的区域之间有多少可重用的缓冲区，
	 * 基于平滑的密度估计。
	 */
	fc_bufs_ahead = NBuffers - fc_bufs_to_lap;
	fc_reusable_buffers_est = (float) fc_bufs_ahead / fc_smoothed_density;

	/*
	 * 跟踪最近缓冲区分配的移动平均值。在这里， 与其说是
	 * 真实平均值，我们想要快速上升、缓慢下降的行为：我们
	 * 立即跟随任何增加。
	 */
	if (fc_smoothed_alloc <= (float) fc_recent_alloc)
		fc_smoothed_alloc = fc_recent_alloc;
	else
		fc_smoothed_alloc += ((float) fc_recent_alloc - fc_smoothed_alloc) /
			fc_smoothing_samples;

	/* 通过GUC缩放估计，以允许更激进的调整。 */
	fc_upcoming_alloc_est = (int) (fc_smoothed_alloc * bgwriter_lru_multiplier);

	/*
	 * 如果recent_alloc在多个循环中保持为零，则smooth_alloc最终将
	 * 下溢为零，且下溢会在某些平台上产生恼人的
	 * 内核警告。一旦upcoming_alloc_est变为零，就没有理由跟踪
	 * 越来越小的smooth_alloc值了，因此将其重置为零以避免此
	 * 综合症。当recent_alloc增加时，它会立即反弹回来。
	 */
	if (fc_upcoming_alloc_est == 0)
		fc_smoothed_alloc = 0;

	/*
	 * 即使在几乎没有缓冲区分配活动的情况下，我们也希望通过缓冲
	 * 区缓存取得少量进展，以便在闲置期间尽可能多地清理可重用缓冲区。
	 *
	 * (scan_whole_pool_milliseconds / BgWriterDelay) 计算在scan_whole_pool
	 * 时间内将调用BGW的次数；将缓冲池切成那么多部分。
	 */
	fc_min_scan_buffers = (int) (NBuffers / (fc_scan_whole_pool_milliseconds / BgWriterDelay));

	if (fc_upcoming_alloc_est < (fc_min_scan_buffers + fc_reusable_buffers_est))
	{
#ifdef BGW_DEBUG
		elog(DEBUG2, "bgwriter: alloc_est=%d too small, using min=%d + reusable_est=%d",
			 fc_upcoming_alloc_est, fc_min_scan_buffers, fc_reusable_buffers_est);
#endif
		fc_upcoming_alloc_est = fc_min_scan_buffers + fc_reusable_buffers_est;
	}

	/*
	 * 现在写出脏的可重用缓冲区，从next_to_clean点向前工作，
	 * 直到我们超过策略扫描，或清理了足够的缓冲区，以匹配我们
	 * 对下一个周期分配要求的估计，或者达到bgwriter_lru_maxpages
	 * 限制。
	 */

	/* 确保我们可以在 SyncOneBuffer 内部处理固定 */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	fc_num_to_scan = fc_bufs_to_lap;
	fc_num_written = 0;
	fc_reusable_buffers = fc_reusable_buffers_est;

	/* 执行LRU扫描 */
	while (fc_num_to_scan > 0 && fc_reusable_buffers < fc_upcoming_alloc_est)
	{
		int			fc_sync_state = fc_SyncOneBuffer(fc_next_to_clean, true,
											   fc_wb_context);

		if (++fc_next_to_clean >= NBuffers)
		{
			fc_next_to_clean = 0;
			fc_next_passes++;
		}
		fc_num_to_scan--;

		if (fc_sync_state & BUF_WRITTEN)
		{
			fc_reusable_buffers++;
			if (++fc_num_written >= bgwriter_lru_maxpages)
			{
				PendingBgWriterStats.maxwritten_clean++;
				break;
			}
		}
		else if (fc_sync_state & BUF_REUSABLE)
			fc_reusable_buffers++;
	}

	PendingBgWriterStats.buf_written_clean += fc_num_written;

#ifdef BGW_DEBUG
	elog(DEBUG1, "bgwriter: recent_alloc=%u smoothed=%.2f delta=%ld ahead=%d density=%.2f reusable_est=%d upcoming_est=%d scanned=%d wrote=%d reusable=%d",
		 fc_recent_alloc, fc_smoothed_alloc, fc_strategy_delta, fc_bufs_ahead,
		 fc_smoothed_density, fc_reusable_buffers_est, fc_upcoming_alloc_est,
		 fc_bufs_to_lap - fc_num_to_scan,
		 fc_num_written,
		 fc_reusable_buffers - fc_reusable_buffers_est);
#endif

	/*
	 * 将上述扫描视为一次新的分配扫描。
	 * 对其密度进行特征化，并基于此更新平滑的密度。这
	 * 有效地在策略和后台写入器都进行一些有用扫描的情况下
	 * 将移动平均周期减半，这很有帮助，因为长时间的记忆在
	 * 密度估计中并不那么理想。
	 */
	fc_new_strategy_delta = fc_bufs_to_lap - fc_num_to_scan;
	fc_new_recent_alloc = fc_reusable_buffers - fc_reusable_buffers_est;
	if (fc_new_strategy_delta > 0 && fc_new_recent_alloc > 0)
	{
		fc_scans_per_alloc = (float) fc_new_strategy_delta / (float) fc_new_recent_alloc;
		fc_smoothed_density += (fc_scans_per_alloc - fc_smoothed_density) /
			fc_smoothing_samples;

#ifdef BGW_DEBUG
		elog(DEBUG2, "bgwriter: cleaner density alloc=%u scan=%ld density=%.2f new smoothed=%.2f",
			 fc_new_recent_alloc, fc_new_strategy_delta,
			 fc_scans_per_alloc, fc_smoothed_density);
#endif
	}

	/* 如果好可以进入休眠则返回true */
	return (fc_bufs_to_lap == 0 && fc_recent_alloc == 0);
}

/*
 * SyncOneBuffer -- 在同步过程中处理单个缓冲区。
 *
 * 如果 skip_recently_used 为真，我们不写当前固定的缓冲区，也不写标记为最近使用的缓冲区，因为这些不是替换候选者。
 *
 * 返回包含以下标志位的位掩码：
 * BUF_WRITTEN: 我们已写入缓冲区。
 * BUF_REUSABLE: 缓冲区可用于替换，即，它的固定计数为 0 而使用计数也为 0。
 *
 * （如果 FlushBuffer 在锁定缓冲区后发现缓冲区是干净的，则 BUF_WRITTEN 可能会错误设置，但我们对此并不太在意。）
 *
 * 注意：调用者必须已执行 ResourceOwnerEnlargeBuffers。
 */
static int fc_SyncOneBuffer(int fc_buf_id, bool fc_skip_recently_used, WritebackContext *fc_wb_context)
{
	BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_buf_id);
	int			fc_result = 0;
	uint32		fc_buf_state;
	BufferTag	fc_tag;

	fc_ReservePrivateRefCountEntry();

	/*
 * 检查缓冲区是否需要写入。
 *
 * 只要我们在记录更改之前先在访问方法中标记页面为脏，就可以在不获取缓冲区内容锁的情况下进行此检查：
 * 如果有人在我们的检查之后标记缓冲区为脏，我们不担心，因为我们的 checkpoint.redo 在即将进行的更改的日志记录之前，因而我们不需要写入这样的脏缓冲区。
 */
	fc_buf_state = LockBufHdr(fc_bufHdr);

	if (BUF_STATE_GET_REFCOUNT(fc_buf_state) == 0 &&
		BUF_STATE_GET_USAGECOUNT(fc_buf_state) == 0)
	{
		fc_result |= BUF_REUSABLE;
	}
	else if (fc_skip_recently_used)
	{
		/* 调用者告诉我们不要写入最近使用的缓冲区 */
		UnlockBufHdr(fc_bufHdr, fc_buf_state);
		return fc_result;
	}

	if (!(fc_buf_state & BM_VALID) || !(fc_buf_state & BM_DIRTY))
	{
		/* 这很干净，所以无需操作 */
		UnlockBufHdr(fc_bufHdr, fc_buf_state);
		return fc_result;
	}

	/*
	 * 固定它，分享锁定，写入它。 （如果缓冲区在我们锁定时是干净的，
	 * FlushBuffer将不会做任何事情。）
	 */
	fc_PinBuffer_Locked(fc_bufHdr);
	LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr), LW_SHARED);

	fc_FlushBuffer(fc_bufHdr, NULL);

	LWLockRelease(BufferDescriptorGetContentLock(fc_bufHdr));

	fc_tag = fc_bufHdr->tag;

	fc_UnpinBuffer(fc_bufHdr, true);

	ScheduleBufferTagForWriteback(fc_wb_context, &fc_tag);

	return fc_result | BUF_WRITTEN;
}

/*
 *		AtEOXact_Buffers - 在事务结束时进行清理。
 *
 *		从PostgreSQL 8.0开始，缓冲区钉住应由
 *		ResourceOwner机制释放。 这个例程只是一个调试
 *		交叉检查，确保没有剩余的钉住。
 */
void AtEOXact_Buffers(bool fc_isCommit)
{
	fc_CheckForBufferLeaks();

	AtEOXact_LocalBuffers(fc_isCommit);

	Assert(PrivateRefCountOverflowed == 0);
}

/*
 * 初始化对共享缓冲池的访问
 *
 * 这个在后端启动期间被调用（无论是独立的还是在
 * postmaster下）。它为此后端访问已经存在的
 * 缓冲池做准备。
 */
void InitBufferPoolAccess(void)
{
	HASHCTL		fc_hash_ctl;

	memset(&PrivateRefCountArray, 0, sizeof(PrivateRefCountArray));

	fc_hash_ctl.keysize = sizeof(int32);
	fc_hash_ctl.entrysize = sizeof(PrivateRefCountEntry);

	PrivateRefCountHash = hash_create("PrivateRefCount", 100, &fc_hash_ctl,
									  HASH_ELEM | HASH_BLOBS);

	/*
	 * AtProcExit_Buffers需要LWLock访问，因此必须在
	 * 后端关闭的相应阶段调用。
	 */
	Assert(MyProc != NULL);
	on_shmem_exit(fc_AtProcExit_Buffers, 0);
}

/*
 * 在后端退出期间，确保我们释放了所有共享缓冲锁，并
 * 断言我们没有剩余的钉住。
 */
static void fc_AtProcExit_Buffers(int fc_code, Datum fc_arg)
{
	AbortBufferIO();
	UnlockBuffers();

	fc_CheckForBufferLeaks();

	/* localbuf.c也需要一个机会 */
	AtProcExit_LocalBuffers();
}

/*
 *		CheckForBufferLeaks - 确保这个后端没有持有缓冲区钉住
 *
 *		从PostgreSQL 8.0开始，缓冲区钉住应由
 *		ResourceOwner机制释放。这个例程只是一个调试
 *		交叉检查，确保没有剩余的钉住。
 */
static void fc_CheckForBufferLeaks(void)
{
#ifdef USE_ASSERT_CHECKING
	int			fc_RefCountErrors = 0;
	PrivateRefCountEntry *fc_res;
	int			fc_i;

	/* 检查数组 */
	for (fc_i = 0; fc_i < REFCOUNT_ARRAY_ENTRIES; fc_i++)
	{
		fc_res = &PrivateRefCountArray[fc_i];

		if (fc_res->buffer != InvalidBuffer)
		{
			PrintBufferLeakWarning(fc_res->buffer);
			fc_RefCountErrors++;
		}
	}

	/* 如果有必要，搜索哈希 */
	if (PrivateRefCountOverflowed)
	{
		HASH_SEQ_STATUS fc_hstat;

		hash_seq_init(&fc_hstat, PrivateRefCountHash);
		while ((fc_res = (PrivateRefCountEntry *) hash_seq_search(&fc_hstat)) != NULL)
		{
			PrintBufferLeakWarning(fc_res->buffer);
			fc_RefCountErrors++;
		}
	}

	Assert(fc_RefCountErrors == 0);
#endif
}

/*
 * 辅助例程，用于在缓冲区意外被钉住时发出警告
 */
void PrintBufferLeakWarning(Buffer fc_buffer)
{
	BufferDesc *fc_buf;
	int32		fc_loccount;
	char	   *fc_path;
	BackendId	fc_backend;
	uint32		fc_buf_state;

	Assert(BufferIsValid(fc_buffer));
	if (BufferIsLocal(fc_buffer))
	{
		fc_buf = GetLocalBufferDescriptor(-fc_buffer - 1);
		fc_loccount = LocalRefCount[-fc_buffer - 1];
		fc_backend = MyBackendId;
	}
	else
	{
		fc_buf = GetBufferDescriptor(fc_buffer - 1);
		fc_loccount = fc_GetPrivateRefCount(fc_buffer);
		fc_backend = InvalidBackendId;
	}

	/* 理论上我们应该在这里锁定bufhdr */
	fc_path = relpathbackend(fc_buf->tag.rnode, fc_backend, fc_buf->tag.forkNum);
	fc_buf_state = pg_atomic_read_u32(&fc_buf->state);
	elog(WARNING,
		 "buffer refcount leak: [%03d] "
		 "(rel=%s, blockNum=%u, flags=0x%x, refcount=%u %d)",
		 fc_buffer, fc_path,
		 fc_buf->tag.blockNum, fc_buf_state & BUF_FLAG_MASK,
		 BUF_STATE_GET_REFCOUNT(fc_buf_state), fc_loccount);
	pfree(fc_path);
}

/*
 * CheckPointBuffers
 *
 * 在检查点时间将缓冲池中的所有脏块刷新到磁盘。
 *
 * 注意：临时关系不参与检查点，因此它们不需要刷新。
 */
void CheckPointBuffers(int fc_flags)
{
	fc_BufferSync(fc_flags);
}


/*
 * 针对bufmgr和smgr级别执行所需的任何准备以进行提交
 */
void BufmgrCommit(void)
{
	/* bufmgr中没有更多操作... */
}

/*
 * BufferGetBlockNumber
 *		返回与缓冲区关联的块号。
 *
 * 注意：
 *		假设缓冲区是有效且被钉住的，否则该
 *		值可能会立即过时...
 */
BlockNumber BufferGetBlockNumber(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;

	Assert(BufferIsPinned(fc_buffer));

	if (BufferIsLocal(fc_buffer))
		fc_bufHdr = GetLocalBufferDescriptor(-fc_buffer - 1);
	else
		fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	/* 已被钉住，所以可以在无自旋锁的情况下读取标签 */
	return fc_bufHdr->tag.blockNum;
}

/*
 * BufferGetTag
 *		返回与缓冲区关联的relfilenode、fork号和块号。
 */
void BufferGetTag(Buffer fc_buffer, RelFileNode *fc_rnode, ForkNumber *fc_forknum,
			 BlockNumber *fc_blknum)
{
	BufferDesc *fc_bufHdr;

	/* 进行与BufferGetBlockNumber相同的检查。 */
	Assert(BufferIsPinned(fc_buffer));

	if (BufferIsLocal(fc_buffer))
		fc_bufHdr = GetLocalBufferDescriptor(-fc_buffer - 1);
	else
		fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	/* 已被钉住，所以可以在无自旋锁的情况下读取标签 */
	*fc_rnode = fc_bufHdr->tag.rnode;
	*fc_forknum = fc_bufHdr->tag.forkNum;
	*fc_blknum = fc_bufHdr->tag.blockNum;
}

/*
 * FlushBuffer
 *		物理写出共享缓冲区。
 *
 * 注意：这实际上只是将缓冲区内容传递给内核；写入磁盘的真实操作将在内核认为合适时发生。这
 * 从我们的角度来看是可以的，因为我们可以从WAL重新做更改。
 * 但是，在我们可以检查点WAL之前，我们需要通过fsync强制将更改写入磁盘。
 *
 * 调用者必须对缓冲区持有钉住并已分享锁定
 * 缓冲区内容。（注意：分享锁不会阻止
 * 更新缓冲区中的提示位，因此在写入
 * 进行时页面可能会改变，但我们假设这不会使已写入的数据无效。）
 *
 * 如果调用者对缓冲区的关系有smgr引用，则作为第二个参数传递它。
 * 如果没有，传NULL。
 */
static void fc_FlushBuffer(BufferDesc *fc_buf, SMgrRelation fc_reln)
{
	XLogRecPtr	fc_recptr;
	ErrorContextCallback fc_errcallback;
	instr_time	fc_io_start,
				fc_io_time;
	Block		fc_bufBlock;
	char	   *fc_bufToWrite;
	uint32		fc_buf_state;

	/*
	 * 尝试启动I/O操作。如果StartBufferIO返回false，则
	 * 其他人会在我们之前刷新缓冲区，因此我们无须做
	 * 任何事情。
	 */
	if (!fc_StartBufferIO(fc_buf, false))
		return;

	/* 为ereport()设置错误追踪支持 */
	fc_errcallback.callback = fc_shared_buffer_write_error_callback;
	fc_errcallback.arg = (void *) fc_buf;
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	/* 查找缓冲区的smgr关系 */
	if (fc_reln == NULL)
		fc_reln = smgropen(fc_buf->tag.rnode, InvalidBackendId);

	TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
										buf->tag.blockNum,
										reln->smgr_rnode.node.spcNode,
										reln->smgr_rnode.node.dbNode,
										reln->smgr_rnode.node.relNode);

	fc_buf_state = LockBufHdr(fc_buf);

	/*
	 * 在持有标题锁的同时运行PageGetLSN，因为我们在所有情况下
	 * 并没有独占地锁定缓冲区。
	 */
	fc_recptr = BufferGetLSN(fc_buf);

	/* 检查在刷新期间块内容是否发生变化。 - vadim 01/17/97 */
	fc_buf_state &= ~BM_JUST_DIRTIED;
	UnlockBufHdr(fc_buf, fc_buf_state);

	/*
	 * 强制XLOG刷新到缓冲区的LSN。这实现了基本的WAL
	 * 规则，即日志更新必须先写入磁盘，然后才能进行描述的数据文件更改。
	 *
	 * 但是，这条规则不适用于无日志的关系，无论如何会在崩溃后丢失。大多数无日志关系页面不带
	 * LSN，因为我们从未为它们发出WAL记录，因此通过缓冲区LSN刷新将无用，但无害。然而，
	 * GiST索引在内部使用LSN跟踪页面拆分，因此无日志GiST页面带有由
	 * GetFakeLSNForUnloggedRel生成的“伪”LSN。虽然不太可能，但伪LSN计数器有可能超过WAL插入点；
	 * 如果发生这种情况，尝试通过该位置刷新WAL将失败，导致系统范围内的灾难性后果。为确保不会发生那种情况，
	 * 如果缓冲区不是永久的，则跳过刷新。
	 */
	if (fc_buf_state & BM_PERMANENT)
		XLogFlush(fc_recptr);

	/*
	 * 现在将缓冲区写入磁盘是安全的。注意，在我们忙于日志刷新时，不应该有其他人能够写入它，
	 * 因为一次只能有一个进程设置BM_IO_IN_PROGRESS位。
	 */
	fc_bufBlock = BufHdrGetBlock(fc_buf);

	/*
	 * 如果需要，更新页面校验和。由于我们只有缓冲区的共享锁，
	 * 其他进程可能会在其中更新提示位，因此如果我们进行校验和检查，
	 * 必须将页面复制到私有存储。
	 */
	fc_bufToWrite = PageSetChecksumCopy((Page) fc_bufBlock, fc_buf->tag.blockNum);

	if (track_io_timing)
		INSTR_TIME_SET_CURRENT(fc_io_start);

	
/*
	 * bufToWrite 是共享缓冲区或适当的副本。
	 */
	smgrwrite(fc_reln,
			  fc_buf->tag.forkNum,
			  fc_buf->tag.blockNum,
			  fc_bufToWrite,
			  false);

	if (track_io_timing)
	{
		INSTR_TIME_SET_CURRENT(fc_io_time);
		INSTR_TIME_SUBTRACT(fc_io_time, fc_io_start);
		pgstat_count_buffer_write_time(INSTR_TIME_GET_MICROSEC(fc_io_time));
		INSTR_TIME_ADD(pgBufferUsage.blk_write_time, fc_io_time);
	}

	pgBufferUsage.shared_blks_written++;

	/*
	 * 将缓冲区标记为干净（除非 BM_JUST_DIRTIED 已被设置）并
	 * 结束 BM_IO_IN_PROGRESS 状态。
	 */
	fc_TerminateBufferIO(fc_buf, true, 0);

	TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(buf->tag.forkNum,
									   buf->tag.blockNum,
									   reln->smgr_rnode.node.spcNode,
									   reln->smgr_rnode.node.dbNode,
									   reln->smgr_rnode.node.relNode);

	/* 弹出错误上下文栈 */
	error_context_stack = fc_errcallback.previous;
}

/*
 * RelationGetNumberOfBlocksInFork
 *		确定指定关系分支中当前页面的数量。
 *
 * 请注意，结果的准确性将取决于关系存储的细节。对于内置的 AM 来说是准确的，但对于外部 AM 可能不是。
 */
BlockNumber RelationGetNumberOfBlocksInFork(Relation fc_relation, ForkNumber fc_forkNum)
{
	if (RELKIND_HAS_TABLE_AM(fc_relation->rd_rel->relkind))
	{
		/*
		 * 并非每个表 AM 使用 BLCKSZ 宽的固定大小块。因此
		 * tableam 返回字节数 - 但对于本例中的这个例程，我们希望得到块的数量。因此进行除法，向上取整。
		 */
		uint64		fc_szbytes;

		fc_szbytes = table_relation_size(fc_relation, fc_forkNum);

		return (fc_szbytes + (BLCKSZ - 1)) / BLCKSZ;
	}
	else if (RELKIND_HAS_STORAGE(fc_relation->rd_rel->relkind))
	{
		return smgrnblocks(RelationGetSmgr(fc_relation), fc_forkNum);
	}
	else
		Assert(false);

	return 0;					/* 保持编译器安静 */
}

/*
 * BufferIsPermanent
 *		判断一个缓冲区在崩溃后是否可能仍然存在。调用者必须持有缓冲区的固定锁。
 */
bool BufferIsPermanent(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;

	/* 本地缓冲区仅用于临时关系。 */
	if (BufferIsLocal(fc_buffer))
		return false;

	/* 确保我们获得了一个真实的缓冲区，并且我们持有其固定锁。 */
	Assert(BufferIsValid(fc_buffer));
	Assert(BufferIsPinned(fc_buffer));

	/*
	 * BM_PERMANENT 在我们持有缓冲区的固定锁时不能更改，因此
	 * 我们无需担心缓冲区头的自旋锁。即使其他人在我们进行此操作时
	 * 更改了缓冲区头的状态，状态也是原子性修改的，因此我们将读取到旧值或新值，而
	 * 不是随机垃圾。
	 */
	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);
	return (pg_atomic_read_u32(&fc_bufHdr->state) & BM_PERMANENT) != 0;
}

/*
 * BufferGetLSNAtomic
 *		使用缓冲区头锁原子性地检索缓冲区的 LSN。
 *		这对于某些可能没有缓冲区独占锁的调用者是必要的。
 */
XLogRecPtr BufferGetLSNAtomic(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);
	char	   *fc_page = BufferGetPage(fc_buffer);
	XLogRecPtr	fc_lsn;
	uint32		fc_buf_state;

	/*
	 * 如果我们不需要锁定以确保正确性，直接快速返回。
	 */
	if (!XLogHintBitIsNeeded() || BufferIsLocal(fc_buffer))
		return PageGetLSN(fc_page);

	/* 确保我们获得了一个真实的缓冲区，并且我们持有其固定锁。 */
	Assert(BufferIsValid(fc_buffer));
	Assert(BufferIsPinned(fc_buffer));

	fc_buf_state = LockBufHdr(fc_bufHdr);
	fc_lsn = PageGetLSN(fc_page);
	UnlockBufHdr(fc_bufHdr, fc_buf_state);

	return fc_lsn;
}

/* ---------------------------------------------------------------------
 *		DropRelFileNodeBuffers
 *
 *		此函数从缓冲池中移除指定关系分支中块号 >= firstDelBlock 的所有页面。
 *		（特别地，对于 firstDelBlock = 0，所有页面都被移除。）
 *		脏页面将被直接丢弃，而不先进行写出处理。
 *		因此，这不可回滚，因此应谨慎使用！
 *
 *		目前，仅在基础文件即将被删除或截断（截断情形需要 firstDelBlock）
 *		时从 smgr.c 调用此函数。受影响页面中的数据因此会被瞬时删除，
 *		而没有写入的必要。
 *		确保删除或截断不丢失任何可能稍后需要的数据是更高级别代码的责任。
 *		此外，更高级别代码还需要确保没有其他进程可能试图
 *		加载更多关系的页面到缓冲区中。
 * --------------------------------------------------------------------
 */
void DropRelFileNodeBuffers(SMgrRelation fc_smgr_reln, ForkNumber *fc_forkNum,
					   int fc_nforks, BlockNumber *fc_firstDelBlock)
{
	int			fc_i;
	int			fc_j;
	RelFileNodeBackend fc_rnode;
	BlockNumber fc_nForkBlock[MAX_FORKNUM];
	uint64		fc_nBlocksToInvalidate = 0;

	fc_rnode = fc_smgr_reln->smgr_rnode;

	/* 如果这是一个本地关系，那就是 localbuf.c 的问题。 */
	if (RelFileNodeBackendIsTemp(fc_rnode))
	{
		if (fc_rnode.backend == MyBackendId)
		{
			for (fc_j = 0; fc_j < fc_nforks; fc_j++)
				DropRelFileNodeLocalBuffers(fc_rnode.node, fc_forkNum[fc_j],
											fc_firstDelBlock[fc_j]);
		}
		return;
	}

	/*
	 * 要从缓冲池中移除指定关系分支的所有页面，我们需要扫描整个缓冲池，
	 * 但我们可以通过查找 BufMapping 表来优化此操作，前提是我们知道每个分支的确切
	 * 大小。确切的大小是必要的，以确保我们不会留下任何缓冲区用于被删除的关系，
	 * 否则后台写入器或检查点器在冲刷对应于不存在文件的缓冲区时可能导致 PANIC
	 * 错误。
	 *
	 * 为了知道确切的大小，我们依靠我们在恢复过程中缓存的每个分支的大小，
	 * 这将优化限制在恢复和备用服务器上，但一旦我们有共享缓存用于
	 * 关系大小，我们可以轻松扩展它。
	 *
	 * 在恢复过程中，我们缓存第一次 lseek(SEEK_END) 返回的值，
	 * 未来的写入操作将保持缓存值的最新状态。见 smgrextend。
	 * 第一次 lseek 的值可能会小于文件中实际存在的块数，
	 * 由于错误的 Linux 内核可能未考虑到最近的写入。
	 * 但这应该没问题，因为在该文件大小之后应该没有任何缓冲区。
	 */
	for (fc_i = 0; fc_i < fc_nforks; fc_i++)
	{
		/* 获取关系分支的块数 */
		fc_nForkBlock[fc_i] = smgrnblocks_cached(fc_smgr_reln, fc_forkNum[fc_i]);

		if (fc_nForkBlock[fc_i] == InvalidBlockNumber)
		{
			fc_nBlocksToInvalidate = InvalidBlockNumber;
			break;
		}

		/* 计算要失效的块数 */
		fc_nBlocksToInvalidate += (fc_nForkBlock[fc_i] - fc_firstDelBlock[fc_i]);
	}

	/*
	 * 仅当要失效的块的总数低于 BUF_DROP_FULL_SCAN_THRESHOLD 时我们才应用该优化。
	 */
	if (BlockNumberIsValid(fc_nBlocksToInvalidate) &&
		fc_nBlocksToInvalidate < BUF_DROP_FULL_SCAN_THRESHOLD)
	{
		for (fc_j = 0; fc_j < fc_nforks; fc_j++)
			fc_FindAndDropRelFileNodeBuffers(fc_rnode.node, fc_forkNum[fc_j],
										  fc_nForkBlock[fc_j], fc_firstDelBlock[fc_j]);
		return;
	}

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_i);
		uint32		fc_buf_state;

		
/*
		 * 通过在尝试锁定缓冲区之前预检查缓冲区标签，我们可以让这个过程稍微快一些；这在典型情况下节省了许多锁定获取。这样做是安全的，因为调用者必须对关系持有 AccessExclusiveLock，或者有其他理由确保没有人正在将新页面加载到缓冲池中。(否则我们可能会完全错过这些页面。)因此，虽然我们查看标签时可能会发生变化，但它不可能变成我们关心的值，只可能从这样的值“远离”。所以假阴性是不可能的，假阳性是安全的，因为在获得缓冲锁定后我们会重新检查。
		 *
		 * 我们也可以检查 forkNum 和 blockNum 以及 rnode，但这样做的增量收益似乎很小。
		 */
		if (!RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rnode.node))
			continue;

		fc_buf_state = LockBufHdr(fc_bufHdr);

		for (fc_j = 0; fc_j < fc_nforks; fc_j++)
		{
			if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rnode.node) &&
				fc_bufHdr->tag.forkNum == fc_forkNum[fc_j] &&
				fc_bufHdr->tag.blockNum >= fc_firstDelBlock[fc_j])
			{
				fc_InvalidateBuffer(fc_bufHdr);	/* 释放自旋锁 */
				break;
			}
		}
		if (fc_j >= fc_nforks)
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}
}

/* ---------------------------------------------------------------------
 *		DropRelFileNodesAllBuffers
 *
 *		此函数从缓冲池中移除指定关系的所有分支的所有页面。等价于对每个关系的每个分支调用 DropRelFileNodeBuffers，firstDelBlock = 0。
 * --------------------------------------------------------------------
 */
void DropRelFileNodesAllBuffers(SMgrRelation *fc_smgr_reln, int fc_nnodes)
{
	int			fc_i;
	int			fc_j;
	int			fc_n = 0;
	SMgrRelation *fc_rels;
	BlockNumber (*fc_block)[MAX_FORKNUM + 1];
	uint64		fc_nBlocksToInvalidate = 0;
	RelFileNode *fc_nodes;
	bool		fc_cached = true;
	bool		fc_use_bsearch;

	if (fc_nnodes == 0)
		return;

	fc_rels = palloc(sizeof(SMgrRelation) * fc_nnodes);	/* 非本地关系 */

	/* 如果这是一个本地关系，那就是 localbuf.c 的问题。 */
	for (fc_i = 0; fc_i < fc_nnodes; fc_i++)
	{
		if (RelFileNodeBackendIsTemp(fc_smgr_reln[fc_i]->smgr_rnode))
		{
			if (fc_smgr_reln[fc_i]->smgr_rnode.backend == MyBackendId)
				DropRelFileNodeAllLocalBuffers(fc_smgr_reln[fc_i]->smgr_rnode.node);
		}
		else
			fc_rels[fc_n++] = fc_smgr_reln[fc_i];
	}

	/*
	 * 如果没有非本地关系，那么我们就完成了。释放内存并返回。
	 */
	if (fc_n == 0)
	{
		pfree(fc_rels);
		return;
	}

	/*
	 * 这用于记住所有关系分支的块数。
	 */
	fc_block = (BlockNumber (*)[MAX_FORKNUM + 1])
		palloc(sizeof(BlockNumber) * fc_n * (MAX_FORKNUM + 1));

	/*
	 * 如果我们知道每个给定关系分支的确切大小，可以避免扫描整个缓冲池。参见 DropRelFileNodeBuffers。
	 */
	for (fc_i = 0; fc_i < fc_n && fc_cached; fc_i++)
	{
		for (fc_j = 0; fc_j <= MAX_FORKNUM; fc_j++)
		{
			/* 获取关系分支的块数。 */
			fc_block[fc_i][fc_j] = smgrnblocks_cached(fc_rels[fc_i], fc_j);

			/* 我们只需要考虑存在的关系分支。 */
			if (fc_block[fc_i][fc_j] == InvalidBlockNumber)
			{
				if (!smgrexists(fc_rels[fc_i], fc_j))
					continue;
				fc_cached = false;
				break;
			}

			/* 计算要使无效的块的总数 */
			fc_nBlocksToInvalidate += fc_block[fc_i][fc_j];
		}
	}

	/*
	 * 仅当要失效的块的总数低于 BUF_DROP_FULL_SCAN_THRESHOLD 时我们才应用该优化。
	 */
	if (fc_cached && fc_nBlocksToInvalidate < BUF_DROP_FULL_SCAN_THRESHOLD)
	{
		for (fc_i = 0; fc_i < fc_n; fc_i++)
		{
			for (fc_j = 0; fc_j <= MAX_FORKNUM; fc_j++)
			{
				/* 忽略不存在的关系分支 */
				if (!BlockNumberIsValid(fc_block[fc_i][fc_j]))
					continue;

				/* 删除特定关系分支的所有缓冲区 */
				fc_FindAndDropRelFileNodeBuffers(fc_rels[fc_i]->smgr_rnode.node,
											  fc_j, fc_block[fc_i][fc_j], 0);
			}
		}

		pfree(fc_block);
		pfree(fc_rels);
		return;
	}

	pfree(fc_block);
	fc_nodes = palloc(sizeof(RelFileNode) * fc_n);	/* 非本地关系 */
	for (fc_i = 0; fc_i < fc_n; fc_i++)
		fc_nodes[fc_i] = fc_rels[fc_i]->smgr_rnode.node;

	/*
	 * 对于需要丢弃的关系数量较少的情况，只需简单遍历，以
	 * 节省bsearch的开销。使用的阈值更像是一个猜测而非
	 * 准确确定的值，因为它依赖于许多因素（CPU和RAM
	 * 速度、共享缓冲区数量等）。
	 */
	fc_use_bsearch = fc_n > RELS_BSEARCH_THRESHOLD;

	/* 如有必要，对rnodes列表进行排序 */
	if (fc_use_bsearch)
		pg_qsort(fc_nodes, fc_n, sizeof(RelFileNode), fc_rnode_comparator);

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		RelFileNode *fc_rnode = NULL;
		BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_i);
		uint32		fc_buf_state;

		/*
		 * 由于在DropRelFileNodeBuffers中，未锁定的预检查应该是安全的，
		 * 并节省一些周期。
		 */

		if (!fc_use_bsearch)
		{
			int			fc_j;

			for (fc_j = 0; fc_j < fc_n; fc_j++)
			{
				if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_nodes[fc_j]))
				{
					fc_rnode = &fc_nodes[fc_j];
					break;
				}
			}
		}
		else
		{
			fc_rnode = bsearch((const void *) &(fc_bufHdr->tag.rnode),
							fc_nodes, fc_n, sizeof(RelFileNode),
							fc_rnode_comparator);
		}

		/* 缓冲区不属于任何给定的relfilenodes；跳过它 */
		if (fc_rnode == NULL)
			continue;

		fc_buf_state = LockBufHdr(fc_bufHdr);
		if (RelFileNodeEquals(fc_bufHdr->tag.rnode, (*fc_rnode)))
			fc_InvalidateBuffer(fc_bufHdr);	/* 释放自旋锁 */
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}

	pfree(fc_nodes);
	pfree(fc_rels);
}

/* ---------------------------------------------------------------------
 *		FindAndDropRelFileNodeBuffers
 *
 *		此函数在BufMapping表中执行查找，并从缓冲池中移除
 *		所有指定关系分支的页面，其块号大于或等于firstDelBlock。
 *		（特别是，当firstDelBlock=0时，所有页面均被移除。）
 * --------------------------------------------------------------------
 */
static void fc_FindAndDropRelFileNodeBuffers(RelFileNode fc_rnode, ForkNumber fc_forkNum,
							  BlockNumber fc_nForkBlock,
							  BlockNumber fc_firstDelBlock)
{
	BlockNumber fc_curBlock;

	for (fc_curBlock = fc_firstDelBlock; fc_curBlock < fc_nForkBlock; fc_curBlock++)
	{
		uint32		fc_bufHash;	/* 标签的哈希值 */
		BufferTag	fc_bufTag;		/* 请求块的标识 */
		LWLock	   *fc_bufPartitionLock;	/* 其缓冲区分区锁 */
		int			fc_buf_id;
		BufferDesc *fc_bufHdr;
		uint32		fc_buf_state;

		/* 创建一个标签以便我们可以查找缓冲区 */
		INIT_BUFFERTAG(fc_bufTag, fc_rnode, fc_forkNum, fc_curBlock);

		/* 确定其哈希代码和分区锁ID */
		fc_bufHash = BufTableHashCode(&fc_bufTag);
		fc_bufPartitionLock = BufMappingPartitionLock(fc_bufHash);

		/* 检查它是否在缓冲池中。如果不在，则不执行任何操作。 */
		LWLockAcquire(fc_bufPartitionLock, LW_SHARED);
		fc_buf_id = BufTableLookup(&fc_bufTag, fc_bufHash);
		LWLockRelease(fc_bufPartitionLock);

		if (fc_buf_id < 0)
			continue;

		fc_bufHdr = GetBufferDescriptor(fc_buf_id);

		/*
		 * 我们需要锁定缓冲区头并重新检查缓冲区是否仍与
		 * 相同的块相关联，因为在我们释放对BufMapping表的锁后，
		 * 缓冲区可能被其他后端加载用于不同关系的块驱逐。
		 */
		fc_buf_state = LockBufHdr(fc_bufHdr);

		if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rnode) &&
			fc_bufHdr->tag.forkNum == fc_forkNum &&
			fc_bufHdr->tag.blockNum >= fc_firstDelBlock)
			fc_InvalidateBuffer(fc_bufHdr);	/* 释放自旋锁 */
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}
}

/* ---------------------------------------------------------------------
 *		DropDatabaseBuffers
 *
 *		此函数移除特定数据库的缓冲缓存中的所有缓冲区。脏页
 *		将被简单地丢弃，而不需先将其写出。当我们销毁一个数据库
 *		时使用此函数，以避免在目录树已不存在时试图将数据刷新
 *		到磁盘中。实现与用于销毁单个关系的DropRelFileNodeBuffers()
 *		非常相似。
 * --------------------------------------------------------------------
 */
void DropDatabaseBuffers(Oid fc_dbid)
{
	int			fc_i;

	/*
	 * 我们不需要考虑本地缓冲区，因为假设目标数据库并不是我们的
	 * 数据库。
	 */

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_i);
		uint32		fc_buf_state;

		/*
		 * 由于在DropRelFileNodeBuffers中，未锁定的预检查应该是安全的，
		 * 并节省一些周期。
		 */
		if (fc_bufHdr->tag.rnode.dbNode != fc_dbid)
			continue;

		fc_buf_state = LockBufHdr(fc_bufHdr);
		if (fc_bufHdr->tag.rnode.dbNode == fc_dbid)
			fc_InvalidateBuffer(fc_bufHdr);	/* 释放自旋锁 */
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}
}

/* -----------------------------------------------------------------
 *		PrintBufferDescs
 *
 *		此函数打印所有缓冲区描述符，仅用于调试
 *		用途。
 * -----------------------------------------------------------------
 */
#ifdef NOT_USED
#ifndef FDD
/* 当前版本中 buf->refcount，buf->flags 这2个变量被buf->state取代
且GetPrivateRefCount这个函数也被删除了，所以注释PrintBufferDescs，
PrintBufferDescs这2个函数
*/
void PrintBufferDescs(void)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NBuffers; ++fc_i)
	{
		BufferDesc *fc_buf = GetBufferDescriptor(fc_i);
		Buffer		fc_b = BufferDescriptorGetBuffer(fc_buf);

		/* 理论上我们应该在这里锁定bufhdr */
		elog(LOG,
			 "[%02d] (freeNext=%d, rel=%s, "
			 "blockNum=%u, flags=0x%x, refcount=%u %d)",
			 fc_i, fc_buf->freeNext,
			 relpathbackend(fc_buf->tag.rnode, InvalidBackendId, fc_buf->tag.forkNum),
			 fc_buf->tag.blockNum, fc_buf->flags,
			 fc_buf->refcount, GetPrivateRefCount(fc_b));
	}
}
#endif //end FDD
#endif

#ifdef NOT_USED
#ifndef FDD
/* 当前版本中 buf->refcount，buf->flags 这2个变量被buf->state取代
且GetPrivateRefCount这个函数也被删除了，所以注释PrintBufferDescs，
PrintBufferDescs这2个函数
*/
void PrintPinnedBufs(void)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NBuffers; ++fc_i)
	{
		BufferDesc *fc_buf = GetBufferDescriptor(fc_i);
		Buffer		fc_b = BufferDescriptorGetBuffer(fc_buf);

		if (GetPrivateRefCount(fc_b) > 0)
		{
			/* 理论上我们应该在这里锁定bufhdr */
			elog(LOG,
				 "[%02d] (freeNext=%d, rel=%s, "
				 "blockNum=%u, flags=0x%x, refcount=%u %d)",
				 fc_i, fc_buf->freeNext,
				 relpathperm(fc_buf->tag.rnode, fc_buf->tag.forkNum),
				 fc_buf->tag.blockNum, fc_buf->flags,
				 fc_buf->refcount, GetPrivateRefCount(fc_b));
		}
	}
}
#endif //end FDD
#endif

/* ---------------------------------------------------------------------
 *		FlushRelationBuffers
 *
 *		此函数将关系的所有脏页写入磁盘
 *		（或更准确地说，写入内核磁盘缓冲区），确保
 *		内核对该关系的视图是最新的。
 *
 *		通常，调用者应该持有目标关系的AccessExclusiveLock，以确保
 *		没有其他后端在进行脏写更多块的操作；效果不能在释放锁后
 *		继续存在。
 *
 *		XXX 当前它顺序搜索缓冲池，应该更改为更聪明的搜索方式。
 *		这个例程在任何性能关键的代码路径中都不使用，因此不值得
 *		为正常路径增加额外的开销以加快速度。
 * --------------------------------------------------------------------
 */
void FlushRelationBuffers(Relation fc_rel)
{
	int			fc_i;
	BufferDesc *fc_bufHdr;

	if (RelationUsesLocalBuffers(fc_rel))
	{
		for (fc_i = 0; fc_i < NLocBuffer; fc_i++)
		{
			uint32		fc_buf_state;

			fc_bufHdr = GetLocalBufferDescriptor(fc_i);
			if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rel->rd_node) &&
				((fc_buf_state = pg_atomic_read_u32(&fc_bufHdr->state)) &
				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
			{
				ErrorContextCallback fc_errcallback;
				Page		fc_localpage;

				fc_localpage = (char *) LocalBufHdrGetBlock(fc_bufHdr);

				/* 为ereport()设置错误追踪支持 */
				fc_errcallback.callback = fc_local_buffer_write_error_callback;
				fc_errcallback.arg = (void *) fc_bufHdr;
				fc_errcallback.previous = error_context_stack;
				error_context_stack = &fc_errcallback;

				PageSetChecksumInplace(fc_localpage, fc_bufHdr->tag.blockNum);

				smgrwrite(RelationGetSmgr(fc_rel),
						  fc_bufHdr->tag.forkNum,
						  fc_bufHdr->tag.blockNum,
						  fc_localpage,
						  false);

				fc_buf_state &= ~(BM_DIRTY | BM_JUST_DIRTIED);
				pg_atomic_unlocked_write_u32(&fc_bufHdr->state, fc_buf_state);

				/* 弹出错误上下文栈 */
				error_context_stack = fc_errcallback.previous;
			}
		}

		return;
	}

	/* 确保我们能够在循环内处理pin */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		uint32		fc_buf_state;

		fc_bufHdr = GetBufferDescriptor(fc_i);

		/*
		 * 由于在DropRelFileNodeBuffers中，未锁定的预检查应该是安全的，
		 * 并节省一些周期。
		 */
		if (!RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rel->rd_node))
			continue;

		fc_ReservePrivateRefCountEntry();

		fc_buf_state = LockBufHdr(fc_bufHdr);
		if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_rel->rd_node) &&
			(fc_buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
		{
			fc_PinBuffer_Locked(fc_bufHdr);
			LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr), LW_SHARED);
			fc_FlushBuffer(fc_bufHdr, RelationGetSmgr(fc_rel));
			LWLockRelease(BufferDescriptorGetContentLock(fc_bufHdr));
			fc_UnpinBuffer(fc_bufHdr, true);
		}
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}
}

/* ---------------------------------------------------------------------
 *		FlushRelationsAllBuffers
 *
 *		此函数将所有指定smgr关系的所有分支的页面从缓冲池中刷
 *		新。它等价于每个关系的每个分支调用FlushRelationBuffers一次。
 *		假设这些关系不使用本地缓冲区。
 * --------------------------------------------------------------------
 */
void FlushRelationsAllBuffers(SMgrRelation *fc_smgrs, int fc_nrels)
{
	int			fc_i;
	SMgrSortArray *fc_srels;
	bool		fc_use_bsearch;

	if (fc_nrels == 0)
		return;

	/* 填充qsort数组 */
	fc_srels = palloc(sizeof(SMgrSortArray) * fc_nrels);

	for (fc_i = 0; fc_i < fc_nrels; fc_i++)
	{
		Assert(!RelFileNodeBackendIsTemp(fc_smgrs[fc_i]->smgr_rnode));

		fc_srels[fc_i].rnode = fc_smgrs[fc_i]->smgr_rnode.node;
		fc_srels[fc_i].srel = fc_smgrs[fc_i];
	}

	/*
	 * 对于需要同步的关系数量较少，节省bsearch的开销。有关详细信息，
	 * 请参见DropRelFileNodesAllBuffers。
	 */
	fc_use_bsearch = fc_nrels > RELS_BSEARCH_THRESHOLD;

	/* 如有必要，对SMgrRelations列表进行排序 */
	if (fc_use_bsearch)
		pg_qsort(fc_srels, fc_nrels, sizeof(SMgrSortArray), fc_rnode_comparator);

	/* 确保我们能够在循环内处理pin */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		SMgrSortArray *fc_srelent = NULL;
		BufferDesc *fc_bufHdr = GetBufferDescriptor(fc_i);
		uint32		fc_buf_state;

		/*
		 * 由于在DropRelFileNodeBuffers中，未锁定的预检查应该是安全的，
		 * 并节省一些周期。
		 */

		if (!fc_use_bsearch)
		{
			int			fc_j;

			for (fc_j = 0; fc_j < fc_nrels; fc_j++)
			{
				if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_srels[fc_j].rnode))
				{
					fc_srelent = &fc_srels[fc_j];
					break;
				}
			}
		}
		else
		{
			fc_srelent = bsearch((const void *) &(fc_bufHdr->tag.rnode),
							  fc_srels, fc_nrels, sizeof(SMgrSortArray),
							  fc_rnode_comparator);
		}

		/* 缓冲区不属于任何给定的relfilenodes；跳过它 */
		if (fc_srelent == NULL)
			continue;

		fc_ReservePrivateRefCountEntry();

		fc_buf_state = LockBufHdr(fc_bufHdr);
		if (RelFileNodeEquals(fc_bufHdr->tag.rnode, fc_srelent->rnode) &&
			(fc_buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
		{
			fc_PinBuffer_Locked(fc_bufHdr);
			LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr), LW_SHARED);
			fc_FlushBuffer(fc_bufHdr, fc_srelent->srel);
			LWLockRelease(BufferDescriptorGetContentLock(fc_bufHdr));
			fc_UnpinBuffer(fc_bufHdr, true);
		}
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}

	pfree(fc_srels);
}

/* ---------------------------------------------------------------------
 *		RelationCopyStorageUsingBuffer
 *
 *		使用bufmgr复制分支的数据。与RelationCopyStorage相同，
 *		但不是使用smgrread和smgrextend，而是使用bufmgr API进行复制。
 *
 *		有关'permanent'参数的详细信息，请参阅CreateAndCopyRelationData()顶部的评论。
 * --------------------------------------------------------------------
 */
static void fc_RelationCopyStorageUsingBuffer(RelFileNode fc_srcnode,
							   RelFileNode fc_dstnode,
							   ForkNumber fc_forkNum, bool fc_permanent)
{
	Buffer		fc_srcBuf;
	Buffer		fc_dstBuf;
	Page		fc_srcPage;
	Page		fc_dstPage;
	bool		fc_use_wal;
	BlockNumber fc_nblocks;
	BlockNumber fc_blkno;
	PGAlignedBlock fc_buf;
	BufferAccessStrategy fc_bstrategy_src;
	BufferAccessStrategy fc_bstrategy_dst;

	/*
	 * 通常，当wal_level > 'minimal'时，我们希望写入WAL，
	 * 但在复制非初始化分支的未记录关系时可以跳过。
	 */
	fc_use_wal = XLogIsNeeded() && (fc_permanent || fc_forkNum == INIT_FORKNUM);

	/* 获取源关系中的块数。 */
	fc_nblocks = smgrnblocks(smgropen(fc_srcnode, InvalidBackendId),
						  fc_forkNum);

	/* 没有什么需要复制；直接返回。 */
	if (fc_nblocks == 0)
		return;

	/*
	 * 在开始逐块复制之前，批量扩展与源关系大小相同的目标关系。
	 */
	memset(fc_buf.data, 0, BLCKSZ);
	smgrextend(smgropen(fc_dstnode, InvalidBackendId), fc_forkNum, fc_nblocks - 1,
			   fc_buf.data, true);

	/* 这是一个批量操作，因此使用缓冲访问策略。 */
	fc_bstrategy_src = GetAccessStrategy(BAS_BULKREAD);
	fc_bstrategy_dst = GetAccessStrategy(BAS_BULKWRITE);

	
/* 遍历源关系文件的每个块。 */
	for (fc_blkno = 0; fc_blkno < fc_nblocks; fc_blkno++)
	{
		CHECK_FOR_INTERRUPTS();

		/* 从源关系中读取块。 */
		fc_srcBuf = ReadBufferWithoutRelcache(fc_srcnode, fc_forkNum, fc_blkno,
										   RBM_NORMAL, fc_bstrategy_src,
										   fc_permanent);
		LockBuffer(fc_srcBuf, BUFFER_LOCK_SHARE);
		fc_srcPage = BufferGetPage(fc_srcBuf);

		fc_dstBuf = ReadBufferWithoutRelcache(fc_dstnode, fc_forkNum, fc_blkno,
										   RBM_ZERO_AND_LOCK, fc_bstrategy_dst,
										   fc_permanent);
		fc_dstPage = BufferGetPage(fc_dstBuf);

		START_CRIT_SECTION();

		/* 将页面数据从源复制到目标。 */
		memcpy(fc_dstPage, fc_srcPage, BLCKSZ);
		MarkBufferDirty(fc_dstBuf);

		/* 记录复制的页面到WAL日志中。 */
		if (fc_use_wal)
			log_newpage_buffer(fc_dstBuf, true);

		END_CRIT_SECTION();

		UnlockReleaseBuffer(fc_dstBuf);
		UnlockReleaseBuffer(fc_srcBuf);
	}

	FreeAccessStrategy(fc_bstrategy_src);
	FreeAccessStrategy(fc_bstrategy_dst);
}

/* ---------------------------------------------------------------------
 *		CreateAndCopyRelationData
 *
 *		创建目标关系存储并将所有数据段从源关系复制到目标。
 *
 *		传递permanent为true用于永久关系，false用于未记录关系。
 *		目前该API不支持临时关系。
 * --------------------------------------------------------------------
 */
void CreateAndCopyRelationData(RelFileNode fc_src_rnode, RelFileNode fc_dst_rnode,
						  bool fc_permanent)
{
	RelFileNodeBackend fc_rnode;
	char		fc_relpersistence;

	/* 设置关系持久性。 */
	fc_relpersistence = fc_permanent ?
		RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;

	/*
	 * 创建并复制关系的所有分支。 在创建数据库期间，我们
	 * 有一个单独的清理机制，用于删除完整的数据库
	 * 目录。 因此，每个单独的关系不需要
	 * 注册以进行清理。
	 */
	RelationCreateStorage(fc_dst_rnode, fc_relpersistence, false);

	/* 复制主分支。 */
	fc_RelationCopyStorageUsingBuffer(fc_src_rnode, fc_dst_rnode, MAIN_FORKNUM,
								   fc_permanent);

	/* 复制存在的额外文件 */
	for (ForkNumber fc_forkNum = MAIN_FORKNUM + 1;
		 fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
	{
		if (smgrexists(smgropen(fc_src_rnode, InvalidBackendId), fc_forkNum))
		{
			smgrcreate(smgropen(fc_dst_rnode, InvalidBackendId), fc_forkNum, false);

			/*
			 * 如果关系是持久的，或者这是未记录关系的初始化文件，
			 * 则创建 WAL 日志。
			 */
			if (fc_permanent || fc_forkNum == INIT_FORKNUM)
				log_smgrcreate(&fc_dst_rnode, fc_forkNum);

			/* 按块复制分支的数据。 */
			fc_RelationCopyStorageUsingBuffer(fc_src_rnode, fc_dst_rnode, fc_forkNum,
										   fc_permanent);
		}
	}

	/* 如果存在，则关闭源和目标 smgr。 */
	fc_rnode.backend = InvalidBackendId;

	fc_rnode.node = fc_src_rnode;
	smgrclosenode(fc_rnode);

	fc_rnode.node = fc_dst_rnode;
	smgrclosenode(fc_rnode);
}

/* ---------------------------------------------------------------------
 *		FlushDatabaseBuffers
 *
 *		此函数将数据库的所有脏页写入磁盘
 *		（更准确地说，写入内核磁盘缓冲区），确保
 *		内核对数据库有最新的视图。
 *
 *		通常，调用者应该持有适当的锁，以确保
 *		没有其他后端在目标数据库中处于活动状态；否则，更多的
 *		页可能会被弄脏。
 *
 *		请注意，我们不担心刷新任何临时关系的页。
 *		假设这些不会引起兴趣。
 * --------------------------------------------------------------------
 */
void FlushDatabaseBuffers(Oid fc_dbid)
{
	int			fc_i;
	BufferDesc *fc_bufHdr;

	/* 确保我们能够在循环内处理pin */
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);

	for (fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		uint32		fc_buf_state;

		fc_bufHdr = GetBufferDescriptor(fc_i);

		/*
		 * 由于在DropRelFileNodeBuffers中，未锁定的预检查应该是安全的，
		 * 并节省一些周期。
		 */
		if (fc_bufHdr->tag.rnode.dbNode != fc_dbid)
			continue;

		fc_ReservePrivateRefCountEntry();

		fc_buf_state = LockBufHdr(fc_bufHdr);
		if (fc_bufHdr->tag.rnode.dbNode == fc_dbid &&
			(fc_buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
		{
			fc_PinBuffer_Locked(fc_bufHdr);
			LWLockAcquire(BufferDescriptorGetContentLock(fc_bufHdr), LW_SHARED);
			fc_FlushBuffer(fc_bufHdr, NULL);
			LWLockRelease(BufferDescriptorGetContentLock(fc_bufHdr));
			fc_UnpinBuffer(fc_bufHdr, true);
		}
		else
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}
}

/*
 * 将先前共享或独占锁定和固定的缓冲区刷新到
 * 操作系统。
 */
void FlushOneBuffer(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;

	/* 当前不需要，但没有根本原因不支持 */
	Assert(!BufferIsLocal(fc_buffer));

	Assert(BufferIsPinned(fc_buffer));

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	Assert(LWLockHeldByMe(BufferDescriptorGetContentLock(fc_bufHdr)));

	fc_FlushBuffer(fc_bufHdr, NULL);
}

/*
 * ReleaseBuffer -- 释放缓冲区上的锁
 */
void ReleaseBuffer(Buffer fc_buffer)
{
	if (!BufferIsValid(fc_buffer))
		elog(ERROR, "bad buffer ID: %d", fc_buffer);

	if (BufferIsLocal(fc_buffer))
	{
		ResourceOwnerForgetBuffer(CurrentResourceOwner, fc_buffer);

		Assert(LocalRefCount[-fc_buffer - 1] > 0);
		LocalRefCount[-fc_buffer - 1]--;
		return;
	}

	fc_UnpinBuffer(GetBufferDescriptor(fc_buffer - 1), true);
}

/*
 * UnlockReleaseBuffer -- 释放缓冲区的内容锁和固定锁
 *
 * 这只是常用组合的简写。
 */
void UnlockReleaseBuffer(Buffer fc_buffer)
{
	LockBuffer(fc_buffer, BUFFER_LOCK_UNLOCK);
	ReleaseBuffer(fc_buffer);
}

/*
 * IncrBufferRefCount
 *		增加我们已经至少固定过一次的缓冲区的固定计数。
 *
 *		此函数不能用于我们没有固定的缓冲区，
 *		因为它不会更改共享缓冲区状态。
 */
void IncrBufferRefCount(Buffer fc_buffer)
{
	Assert(BufferIsPinned(fc_buffer));
	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
	if (BufferIsLocal(fc_buffer))
		LocalRefCount[-fc_buffer - 1]++;
	else
	{
		PrivateRefCountEntry *fc_ref;

		fc_ref = fc_GetPrivateRefCountEntry(fc_buffer, true);
		Assert(fc_ref != NULL);
		fc_ref->refcount++;
	}
	ResourceOwnerRememberBuffer(CurrentResourceOwner, fc_buffer);
}

/*
 * MarkBufferDirtyHint
 *
 *	将缓冲区标记为脏，以进行非关键性更改。
 *
 * 这基本上与 MarkBufferDirty 相同，除了：
 *
 * 1. 调用者不写 WAL；因此如果启用了校验和，我们可能需要
 *	  写入一个 XLOG_FPI_FOR_HINT WAL 记录，以防止撕裂页。
 * 2. 调用者可能仅对缓冲区的内容锁有共享锁，而非独占锁。
 * 3. 该函数不保证缓冲区始终标记为脏
 *	  （由于竞争条件），因此不能用于重要更改。
 */
void MarkBufferDirtyHint(Buffer fc_buffer, bool fc_buffer_std)
{
	BufferDesc *fc_bufHdr;
	Page		fc_page = BufferGetPage(fc_buffer);

	if (!BufferIsValid(fc_buffer))
		elog(ERROR, "bad buffer ID: %d", fc_buffer);

	if (BufferIsLocal(fc_buffer))
	{
		MarkLocalBufferDirty(fc_buffer);
		return;
	}

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	Assert(fc_GetPrivateRefCount(fc_buffer) > 0);
	/* 在这里，共享锁或独占锁均可 */
	Assert(LWLockHeldByMe(BufferDescriptorGetContentLock(fc_bufHdr)));

	/*
	 * 如果我们在提交了添加/删除多个元组的
	 * 事务后进行第一次扫描，这个例程可能会在同一页面上
	 * 被调用多次。因此，如果缓冲区已经脏，就尽可能快。我们
	 * 通过不获取自旋锁来实现这一点，如果状态位看起来已经
	 * 设置。由于我们在解锁状态下进行此测试，因此可能
	 * 未能注意到标志刚被清除，并未能重置它们，
	 * 由于内存排序问题。但由于此函数
	 * 仅用于在失败写出数据不会造成伤害的情况下，因此
	 * 并不重要。
	 */
	if ((pg_atomic_read_u32(&fc_bufHdr->state) & (BM_DIRTY | BM_JUST_DIRTIED)) !=
		(BM_DIRTY | BM_JUST_DIRTIED))
	{
		XLogRecPtr	fc_lsn = InvalidXLogRecPtr;
		bool		fc_dirtied = false;
		bool		fc_delayChkptFlags = false;
		uint32		fc_buf_state;

		/*
		 * 如果我们需要保护提示位更新免受撕裂写入的影响，请 WAL 记录一个
		 * 页的完整图像。仅在提示位更新是自上次检查点以来
		 * 对页的第一个更改时，此完整页图像才是必要的。
		 *
		 * 我们在这里不检查 full_page_writes，因为当我们调用
		 * XLogInsert() 时，这个逻辑已包含，因为值动态变化。
		 */
		if (XLogHintBitIsNeeded() &&
			(pg_atomic_read_u32(&fc_bufHdr->state) & BM_PERMANENT))
		{
			/*
			 * 如果由于特定于文件节点的条件或处于恢复状态而
			 * 不能写入 WAL，请勿弄脏页面。我们可以设置提示，
			 * 只是不能因而弄脏页面，以便在我们驱逐页面或关机时
			 * 遗失提示。
			 *
			 * 请参阅 src/backend/storage/page/README 以获取更长的讨论。
			 */
			if (RecoveryInProgress() ||
				RelFileNodeSkippingWAL(fc_bufHdr->tag.rnode))
				return;

			
/*
			 * 如果块已经被修改为脏，因为我们要么进行了更改，要么已经设置了提示，那么我们就不需要写入完整的页面图像。请注意，积极清理由于提示位设置而变脏的块会增加调用频率。批量设置提示位会减少调用频率...
			 *
			 * 我们必须在标记缓冲区为脏之前发出WAL记录。否则，我们可能会在写入WAL之前写入页面。这会导致竞争条件，因为在写入WAL记录和标记缓冲区为脏之间可能会发生检查点。我们通过一种解决方法来解决这个问题，但该方法在事务提交时已经在使用，以防止竞争条件。基本上，我们只是在标记缓冲区为脏之前，阻止检查点WAL记录的写入。在标记为脏之前，我们不会启动检查点刷新，因此我们的检查点必须将更改成功刷新到磁盘，否则检查点将永远不会被写入，因此崩溃恢复将修复。
			 *
			 * 我们可能在没有xid的情况下进入这里，因此CreateCheckPoint等待虚拟事务而不是完整事务ID是至关重要的。
			 */
			Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0);
			MyProc->delayChkptFlags |= DELAY_CHKPT_START;
			fc_delayChkptFlags = true;
			fc_lsn = XLogSaveBufferForHint(fc_buffer, fc_buffer_std);
		}

		fc_buf_state = LockBufHdr(fc_bufHdr);

		Assert(BUF_STATE_GET_REFCOUNT(fc_buf_state) > 0);

		if (!(fc_buf_state & BM_DIRTY))
		{
			fc_dirtied = true;		/* 意味着“将被此操作弄脏”的意思 */

			/*
			 * 如果我们写入了备份块，则设置页面LSN。我们在仅持有共享锁时不应该设置此项，但只要我们以某种方式序列化它就可以了。我们选择在持有缓冲区头锁时设置LSN，这会导致任何仅持有共享锁的LSN读取者在使用PageGetLSN()之前也必须获取缓冲区头锁，这在BufferGetLSNAtomic()中得到了强制执行。
			 *
			 * 如果启用了校验和，您可能会认为我们应该在这里重置校验和。那将在此检查点周期的稍后时间页面写入时发生。
			 */
			if (!XLogRecPtrIsInvalid(fc_lsn))
				PageSetLSN(fc_page, fc_lsn);
		}

		fc_buf_state |= BM_DIRTY | BM_JUST_DIRTIED;
		UnlockBufHdr(fc_bufHdr, fc_buf_state);

		if (fc_delayChkptFlags)
			MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;

		if (fc_dirtied)
		{
			VacuumPageDirty++;
			pgBufferUsage.shared_blks_dirtied++;
			if (VacuumCostActive)
				VacuumCostBalance += VacuumCostPageDirty;
		}
	}
}

/*
 * 为共享缓冲区释放缓冲内容锁。
 *
 * 用于在错误后进行清理。
 *
 * 目前，我们可以期待lwlock.c的LWLockReleaseAll()负责释放缓冲内容锁；我们这里只需要处理清除任何正在进行的PIN_COUNT请求。
 */
void UnlockBuffers(void)
{
	BufferDesc *fc_buf = PinCountWaitBuf;

	if (fc_buf)
	{
		uint32		fc_buf_state;

		fc_buf_state = LockBufHdr(fc_buf);

		/*
		 * 如果标志位未设置则不要抱怨；它可能已被重置，但我们在收到信号之前收到了取消/死中断。
		 */
		if ((fc_buf_state & BM_PIN_COUNT_WAITER) != 0 &&
			fc_buf->wait_backend_pgprocno == MyProc->pgprocno)
			fc_buf_state &= ~BM_PIN_COUNT_WAITER;

		UnlockBufHdr(fc_buf, fc_buf_state);

		PinCountWaitBuf = NULL;
	}
}

/*
 * 获取或释放缓冲区的content_lock。
 */
void LockBuffer(Buffer fc_buffer, int fc_mode)
{
	BufferDesc *fc_buf;

	Assert(BufferIsPinned(fc_buffer));
	if (BufferIsLocal(fc_buffer))
		return;					/* 本地缓冲区不需要锁 */

	fc_buf = GetBufferDescriptor(fc_buffer - 1);

	if (fc_mode == BUFFER_LOCK_UNLOCK)
		LWLockRelease(BufferDescriptorGetContentLock(fc_buf));
	else if (fc_mode == BUFFER_LOCK_SHARE)
		LWLockAcquire(BufferDescriptorGetContentLock(fc_buf), LW_SHARED);
	else if (fc_mode == BUFFER_LOCK_EXCLUSIVE)
		LWLockAcquire(BufferDescriptorGetContentLock(fc_buf), LW_EXCLUSIVE);
	else
		elog(ERROR, "unrecognized buffer lock mode: %d", fc_mode);
}

/*
 * 获取缓冲区的content_lock，但只有在我们不需要等待的情况下。
 *
 * 这假设调用者希望使用BUFFER_LOCK_EXCLUSIVE模式。
 */
bool ConditionalLockBuffer(Buffer fc_buffer)
{
	BufferDesc *fc_buf;

	Assert(BufferIsPinned(fc_buffer));
	if (BufferIsLocal(fc_buffer))
		return true;			/* 视为我们已获得它 */

	fc_buf = GetBufferDescriptor(fc_buffer - 1);

	return LWLockConditionalAcquire(BufferDescriptorGetContentLock(fc_buf),
									LW_EXCLUSIVE);
}

/*
 * LockBufferForCleanup - 锁定缓冲区以准备删除项目
 *
 * 只有当调用者(a)对缓冲区持有独占锁且(b)观察到没有其他后端对缓冲区持有引脚时，才能从磁盘页面删除项目。如果存在引脚，则其他后端可能对缓冲区有一个指针（例如，对项目的堆扫描引用 - 请参阅README以获取更多详细信息）。但是，如果在清理开始后添加引脚也是可以的；新到达的后端在我们释放独占锁之前将无法查看页面。
 *
 * 要实施此协议，想要删除的者必须固定缓冲区，然后调用LockBufferForCleanup()。LockBufferForCleanup()与LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE)类似，除了它会循环直至成功观察到引脚计数 = 1。
 */
void LockBufferForCleanup(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;
	char	   *fc_new_status = NULL;
	TimestampTz fc_waitStart = 0;
	bool		fc_logged_recovery_conflict = false;

	Assert(BufferIsPinned(fc_buffer));
	Assert(PinCountWaitBuf == NULL);

	if (BufferIsLocal(fc_buffer))
	{
		/* 应该恰好有一个引脚 */
		if (LocalRefCount[-fc_buffer - 1] != 1)
			elog(ERROR, "incorrect local pin count: %d",
				 LocalRefCount[-fc_buffer - 1]);
		/* 没有其他人需要等待 */
		return;
	}

	/* 应该恰好有一个本地引脚 */
	if (fc_GetPrivateRefCount(fc_buffer) != 1)
		elog(ERROR, "incorrect local pin count: %d",
			 fc_GetPrivateRefCount(fc_buffer));

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	for (;;)
	{
		uint32		fc_buf_state;

		/* 尝试获取锁 */
		LockBuffer(fc_buffer, BUFFER_LOCK_EXCLUSIVE);
		fc_buf_state = LockBufHdr(fc_bufHdr);

		Assert(BUF_STATE_GET_REFCOUNT(fc_buf_state) > 0);
		if (BUF_STATE_GET_REFCOUNT(fc_buf_state) == 1)
		{
			/* 成功获得固定计数为 1 的排他锁 */
			UnlockBufHdr(fc_bufHdr, fc_buf_state);

			/*
			 * 如果恢复冲突在缓冲区引脚上得到解决，但启动过程等待的时间
			 * 超过了 deadlock_timeout，则发出日志消息。
			 */
			if (fc_logged_recovery_conflict)
				LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
									fc_waitStart, GetCurrentTimestamp(),
									NULL, false);

			/* 报告状态更改为非等待状态 */
			if (fc_new_status)
			{
				set_ps_display(fc_new_status);
				pfree(fc_new_status);
			}
			return;
		}
		/* 失败，因此将自己标记为等待 pincount 1 */
		if (fc_buf_state & BM_PIN_COUNT_WAITER)
		{
			UnlockBufHdr(fc_bufHdr, fc_buf_state);
			LockBuffer(fc_buffer, BUFFER_LOCK_UNLOCK);
			elog(ERROR, "multiple backends attempting to wait for pincount 1");
		}
		fc_bufHdr->wait_backend_pgprocno = MyProc->pgprocno;
		PinCountWaitBuf = fc_bufHdr;
		fc_buf_state |= BM_PIN_COUNT_WAITER;
		UnlockBufHdr(fc_bufHdr, fc_buf_state);
		LockBuffer(fc_buffer, BUFFER_LOCK_UNLOCK);

		/* 等待被 UnpinBuffer() 信号唤醒 */
		if (InHotStandby)
		{
			/* 报告状态更改为等待状态 */
			if (update_process_title && fc_new_status == NULL)
			{
				const char *fc_old_status;
				int			fc_len;

				fc_old_status = get_ps_display(&fc_len);
				fc_new_status = (char *) palloc(fc_len + 8 + 1);
				memcpy(fc_new_status, fc_old_status, fc_len);
				strcpy(fc_new_status + fc_len, " waiting");
				set_ps_display(fc_new_status);
				fc_new_status[fc_len] = '\0'; /* 截断掉 " waiting" */
			}

			/*
			 * 如果启动过程在缓冲区引脚上等待恢复冲突的时间超过
			 * deadlock_timeout，则发出日志消息。
			 *
			 * 如果是第一次通过则跳过此步骤，因为在这种情况下启动过程
			 * 尚未开始等待。因此，等待开始的时间戳在此逻辑之后设置。
			 */
			if (fc_waitStart != 0 && !fc_logged_recovery_conflict)
			{
				TimestampTz fc_now = GetCurrentTimestamp();

				if (TimestampDifferenceExceeds(fc_waitStart, fc_now,
											   DeadlockTimeout))
				{
					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
										fc_waitStart, fc_now, NULL, true);
					fc_logged_recovery_conflict = true;
				}
			}

			/*
			 * 如果启用了日志记录并且是第一次通过，则设置等待开始时间戳。
			 */
			if (log_recovery_conflict_waits && fc_waitStart == 0)
				fc_waitStart = GetCurrentTimestamp();

			/* 发布启动过程等待的 bufid */
			SetStartupBufferPinWaitBufId(fc_buffer - 1);
			/* 设置警报，然后等待被 UnpinBuffer() 信号唤醒 */
			ResolveRecoveryConflictWithBufferPin();
			/* 重置发布的 bufid */
			SetStartupBufferPinWaitBufId(-1);
		}
		else
			ProcWaitForSignal(PG_WAIT_BUFFER_PIN);

		/*
		 * 移除标记我们为等待者的标志。通常，这将不再被设置，
		 * 但 ProcWaitForSignal() 也可能因为其他信号而返回。
		 * 我们小心地确保仅在我们是等待者时重置该标志，
		 * 理论上来说，其他后端可能已经开始等待。由于表级锁定的
		 * 关系，目前的使用情况是不可能的，但谨慎起见。
		 */
		fc_buf_state = LockBufHdr(fc_bufHdr);
		if ((fc_buf_state & BM_PIN_COUNT_WAITER) != 0 &&
			fc_bufHdr->wait_backend_pgprocno == MyProc->pgprocno)
			fc_buf_state &= ~BM_PIN_COUNT_WAITER;
		UnlockBufHdr(fc_bufHdr, fc_buf_state);

		PinCountWaitBuf = NULL;
		/* 循环回去再试一次 */
	}
}

/*
 * 从 RecoveryConflictInterrupt 处理程序中检查，当启动
 * 过程请求取消所有阻塞它的引脚持有者时。
 */
bool HoldingBufferPinThatDelaysRecovery(void)
{
	int			fc_bufid = GetStartupBufferPinWaitBufId();

	/*
	 * 如果我们慢慢被唤醒，则可能启动过程已经被其他后端唤醒
	 * 在我们到达这里之前。同样也可能是通过多个中断或在不适当
	 * 的时间到达这里，因此如果 bufid 未设置，则确保不做任何事情。
	 */
	if (fc_bufid < 0)
		return false;

	if (fc_GetPrivateRefCount(fc_bufid + 1) > 0)
		return true;

	return false;
}

/*
 * ConditionalLockBufferForCleanup - 如上所述，但不等待获取锁
 *
 * 我们不会循环，而是只检查一次引脚计数是否正常。如果
 * 不正常，则返回 false 且不持有锁。
 */
bool ConditionalLockBufferForCleanup(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;
	uint32		fc_buf_state,
				fc_refcount;

	Assert(BufferIsValid(fc_buffer));

	if (BufferIsLocal(fc_buffer))
	{
		fc_refcount = LocalRefCount[-fc_buffer - 1];
		/* 应该恰好有一个引脚 */
		Assert(fc_refcount > 0);
		if (fc_refcount != 1)
			return false;
		/* 没有其他人需要等待 */
		return true;
	}

	/* 应该恰好有一个本地引脚 */
	fc_refcount = fc_GetPrivateRefCount(fc_buffer);
	Assert(fc_refcount);
	if (fc_refcount != 1)
		return false;

	/* 尝试获取锁 */
	if (!ConditionalLockBuffer(fc_buffer))
		return false;

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);
	fc_buf_state = LockBufHdr(fc_bufHdr);
	fc_refcount = BUF_STATE_GET_REFCOUNT(fc_buf_state);

	Assert(fc_refcount > 0);
	if (fc_refcount == 1)
	{
		/* 成功获得固定计数为 1 的排他锁 */
		UnlockBufHdr(fc_bufHdr, fc_buf_state);
		return true;
	}

	/* 失败，因此释放锁 */
	UnlockBufHdr(fc_bufHdr, fc_buf_state);
	LockBuffer(fc_buffer, BUFFER_LOCK_UNLOCK);
	return false;
}

/*
 * IsBufferCleanupOK - 如上所述，但我们已经持有锁
 *
 * 检查是否可以对我们已经锁定的缓冲区执行清理。
 * 如果我们观察到引脚计数为 1，我们的独占锁恰好是清理锁，
 * 并且我们可以继续进行任何在原本寻求清理锁时允许的操作。
 */
bool IsBufferCleanupOK(Buffer fc_buffer)
{
	BufferDesc *fc_bufHdr;
	uint32		fc_buf_state;

	Assert(BufferIsValid(fc_buffer));

	if (BufferIsLocal(fc_buffer))
	{
		/* 应该恰好有一个引脚 */
		if (LocalRefCount[-fc_buffer - 1] != 1)
			return false;
		/* 没有其他人需要等待 */
		return true;
	}

	/* 应该恰好有一个本地引脚 */
	if (fc_GetPrivateRefCount(fc_buffer) != 1)
		return false;

	fc_bufHdr = GetBufferDescriptor(fc_buffer - 1);

	/* 调用者必须持有缓冲区的独占锁 */
	Assert(LWLockHeldByMeInMode(BufferDescriptorGetContentLock(fc_bufHdr),
								LW_EXCLUSIVE));

	fc_buf_state = LockBufHdr(fc_bufHdr);

	Assert(BUF_STATE_GET_REFCOUNT(fc_buf_state) > 0);
	if (BUF_STATE_GET_REFCOUNT(fc_buf_state) == 1)
	{
		/* 引脚计数是正常的。 */
		UnlockBufHdr(fc_bufHdr, fc_buf_state);
		return true;
	}

	UnlockBufHdr(fc_bufHdr, fc_buf_state);
	return false;
}


/*
 *	缓冲区 I/O 处理的功能
 *
 *	注意：我们假设嵌套缓冲区 I/O 从未发生。
 *	即每个进程最多只设置一个 BM_IO_IN_PROGRESS 标志。
 *
 *	还要注意，这些仅用于共享缓冲区，而不是本地缓冲区。
 */

/*
 * WaitIO -- 阻塞直到 'buf' 上的 IO_IN_PROGRESS 标志被清除。
 */
static void fc_WaitIO(BufferDesc *fc_buf)
{
	ConditionVariable *fc_cv = BufferDescriptorGetIOCV(fc_buf);

	ConditionVariablePrepareToSleep(fc_cv);
	for (;;)
	{
		uint32		fc_buf_state;

		/*
		 * 这里可能不需要获取自旋锁来检查标志，
		 * 但由于此测试对于正确性至关重要，我们最好还是谨慎一些。
		 */
		fc_buf_state = LockBufHdr(fc_buf);
		UnlockBufHdr(fc_buf, fc_buf_state);

		if (!(fc_buf_state & BM_IO_IN_PROGRESS))
			break;
		ConditionVariableSleep(fc_cv, WAIT_EVENT_BUFFER_IO);
	}
	ConditionVariableCancelSleep();
}

/*
 * StartBufferIO: 开始对该缓冲区进行 I/O
 *	（假设）
 *	我的进程未执行任何 I/O
 *	该缓冲区已引脚
 *
 * 在某些情况下，可能会出现多个后端同时尝试相同 I/O 操作的竞争条件。
 * 如果其他人已经开始对该缓冲区进行 I/O，则我们将在 I/O 条件变量
 * 上阻塞直到其完成。
 *
 * 仅对未设置 BM_VALID 的缓冲区尝试输入操作，
 * 仅对设置了 BM_VALID 和 BM_DIRTY 的缓冲区尝试输出操作，
 * 因此我们始终可以判断工作是否已经完成。
 *
 * 如果我们成功地将缓冲区标记为 I/O 忙，则返回 true，
 * 如果其他人已经完成了工作，则返回 false。
 */
static bool fc_StartBufferIO(BufferDesc *fc_buf, bool fc_forInput)
{
	uint32		fc_buf_state;

	Assert(!InProgressBuf);

	for (;;)
	{
		fc_buf_state = LockBufHdr(fc_buf);

		if (!(fc_buf_state & BM_IO_IN_PROGRESS))
			break;
		UnlockBufHdr(fc_buf, fc_buf_state);
		fc_WaitIO(fc_buf);
	}

	/* 一旦到达这里，表明该缓冲区上绝对没有正在进行的 I/O */

	if (fc_forInput ? (fc_buf_state & BM_VALID) : !(fc_buf_state & BM_DIRTY))
	{
		/* 其他人已经完成了 I/O */
		UnlockBufHdr(fc_buf, fc_buf_state);
		return false;
	}

	fc_buf_state |= BM_IO_IN_PROGRESS;
	UnlockBufHdr(fc_buf, fc_buf_state);

	InProgressBuf = fc_buf;
	IsForInput = fc_forInput;

	return true;
}

/*
 * TerminateBufferIO: 释放我们正在进行 I/O 的缓冲区
 *	（假设）
 *	我的进程正在为该缓冲区执行 I/O
 *	缓冲区的 BM_IO_IN_PROGRESS 位被设置
 *	该缓冲区已引脚
 *
 * 如果 clear_dirty 为真并且 BM_JUST_DIRTIED 未设置，则清除
 * 缓冲区的 BM_DIRTY 标志。这在结束成功写入时是适当的。
 * 对 BM_JUST_DIRTIED 的检查是必要的，以避免在我们写入时
 * 将缓冲区标记为干净。
 *
 * set_flag_bits 将使用 OR 操作应用到缓冲区的标志中。
 * 在失败情况下，它必须包括 BM_IO_ERROR。在成功完成的情况下，
 * 它可以为 0，或者如果我们刚完成读取页面，则为 BM_VALID。
 */
static void fc_TerminateBufferIO(BufferDesc *fc_buf, bool fc_clear_dirty, uint32 set_flag_bits)
{
	uint32		fc_buf_state;

	Assert(fc_buf == InProgressBuf);

	fc_buf_state = LockBufHdr(fc_buf);

	Assert(fc_buf_state & BM_IO_IN_PROGRESS);

	fc_buf_state &= ~(BM_IO_IN_PROGRESS | BM_IO_ERROR);
	if (fc_clear_dirty && !(fc_buf_state & BM_JUST_DIRTIED))
		fc_buf_state &= ~(BM_DIRTY | BM_CHECKPOINT_NEEDED);

	fc_buf_state |= set_flag_bits;
	UnlockBufHdr(fc_buf, fc_buf_state);

	InProgressBuf = NULL;

	ConditionVariableBroadcast(BufferDescriptorGetIOCV(fc_buf));
}


/*
 * AbortBufferIO: 在错误发生后清理任何活跃的缓冲区I/O。
 *
 * 	我们可能持有的所有LWLocks都已释放，
 *	但我们尚未释放缓冲区锁，因此缓冲区仍被锁定。
 *
 *	如果I/O正在进行，我们总是设置BM_IO_ERROR，尽管错误条件可能与I/O无关。
 */
void AbortBufferIO(void)
{
	BufferDesc *fc_buf = InProgressBuf;

	if (fc_buf)
	{
		uint32		fc_buf_state;

		fc_buf_state = LockBufHdr(fc_buf);
		Assert(fc_buf_state & BM_IO_IN_PROGRESS);
		if (IsForInput)
		{
			Assert(!(fc_buf_state & BM_DIRTY));

			/* 我们最好不要认为缓冲区是有效的 */
			Assert(!(fc_buf_state & BM_VALID));
			UnlockBufHdr(fc_buf, fc_buf_state);
		}
		else
		{
			Assert(fc_buf_state & BM_DIRTY);
			UnlockBufHdr(fc_buf, fc_buf_state);
			/* 如果这不是第一次失败，发出通知... */
			if (fc_buf_state & BM_IO_ERROR)
			{
				/* 缓冲区已锁定，因此我们可以在不使用自旋锁的情况下读取标签 */
				char	   *fc_path;

				fc_path = relpathperm(fc_buf->tag.rnode, fc_buf->tag.forkNum);
				ereport(WARNING,
						(errcode(ERRCODE_IO_ERROR),
						 errmsg("could not write block %u of %s",
								fc_buf->tag.blockNum, fc_path),
						 errdetail("Multiple failures --- write error might be permanent.")));
				pfree(fc_path);
			}
		}
		fc_TerminateBufferIO(fc_buf, false, BM_IO_ERROR);
	}
}

/*
 * 发生错误时的上下文回调，用于共享缓冲区写入过程中的错误。
 */
static void fc_shared_buffer_write_error_callback(void *fc_arg)
{
	BufferDesc *fc_bufHdr = (BufferDesc *) fc_arg;

	/* 缓冲区已锁定，因此我们可以在不锁定自旋锁的情况下读取标签 */
	if (fc_bufHdr != NULL)
	{
		char	   *fc_path = relpathperm(fc_bufHdr->tag.rnode, fc_bufHdr->tag.forkNum);

		errcontext("writing block %u of relation %s",
				   fc_bufHdr->tag.blockNum, fc_path);
		pfree(fc_path);
	}
}

/*
 * 发生错误时的上下文回调，用于本地缓冲区写入过程中的错误。
 */
static void fc_local_buffer_write_error_callback(void *fc_arg)
{
	BufferDesc *fc_bufHdr = (BufferDesc *) fc_arg;

	if (fc_bufHdr != NULL)
	{
		char	   *fc_path = relpathbackend(fc_bufHdr->tag.rnode, MyBackendId,
										  fc_bufHdr->tag.forkNum);

		errcontext("writing block %u of relation %s",
				   fc_bufHdr->tag.blockNum, fc_path);
		pfree(fc_path);
	}
}

/*
 * RelFileNode qsort/bsearch 比较器；参见 RelFileNodeEquals。
 */
static int fc_rnode_comparator(const void *fc_p1, const void *fc_p2)
{
	RelFileNode fc_n1 = *(const RelFileNode *) fc_p1;
	RelFileNode fc_n2 = *(const RelFileNode *) fc_p2;

	if (fc_n1.relNode < fc_n2.relNode)
		return -1;
	else if (fc_n1.relNode > fc_n2.relNode)
		return 1;

	if (fc_n1.dbNode < fc_n2.dbNode)
		return -1;
	else if (fc_n1.dbNode > fc_n2.dbNode)
		return 1;

	if (fc_n1.spcNode < fc_n2.spcNode)
		return -1;
	else if (fc_n1.spcNode > fc_n2.spcNode)
		return 1;
	else
		return 0;
}

/*
 * 锁定缓冲区头 - 在缓冲区状态中设置 BM_LOCKED。
 */
uint32 LockBufHdr(BufferDesc *fc_desc)
{
	SpinDelayStatus fc_delayStatus;
	uint32		fc_old_buf_state;

	init_local_spin_delay(&fc_delayStatus);

	while (true)
	{
		/* 设置 BM_LOCKED 标志 */
		fc_old_buf_state = pg_atomic_fetch_or_u32(&fc_desc->state, BM_LOCKED);
		/* 如果之前没有设置，我们就可以了 */
		if (!(fc_old_buf_state & BM_LOCKED))
			break;
		perform_spin_delay(&fc_delayStatus);
	}
	finish_spin_delay(&fc_delayStatus);
	return fc_old_buf_state | BM_LOCKED;
}

/*
 * 等待直到 BM_LOCKED 标志不再设置，并返回缓冲区在那时的状态。
 *
 * 很明显，在值返回时，缓冲区可能已经被锁定，因此
 * 这在 CAS 风格的循环中特别有用。
 */
static uint32 fc_WaitBufHdrUnlocked(BufferDesc *fc_buf)
{
	SpinDelayStatus fc_delayStatus;
	uint32		fc_buf_state;

	init_local_spin_delay(&fc_delayStatus);

	fc_buf_state = pg_atomic_read_u32(&fc_buf->state);

	while (fc_buf_state & BM_LOCKED)
	{
		perform_spin_delay(&fc_delayStatus);
		fc_buf_state = pg_atomic_read_u32(&fc_buf->state);
	}

	finish_spin_delay(&fc_delayStatus);

	return fc_buf_state;
}

/*
 * BufferTag 比较器。
 */
static inline int fc_buffertag_comparator(const BufferTag *fc_ba, const BufferTag *fc_bb)
{
	int			fc_ret;

	fc_ret = fc_rnode_comparator(&fc_ba->rnode, &fc_bb->rnode);

	if (fc_ret != 0)
		return fc_ret;

	if (fc_ba->forkNum < fc_bb->forkNum)
		return -1;
	if (fc_ba->forkNum > fc_bb->forkNum)
		return 1;

	if (fc_ba->blockNum < fc_bb->blockNum)
		return -1;
	if (fc_ba->blockNum > fc_bb->blockNum)
		return 1;

	return 0;
}

/*
 * 决定检查点写出顺序的比较器。
 *
 * 首先比较表空间是很重要的，因为在表空间之间平衡
 * 写入的逻辑依赖于此。
 */
static inline int fc_ckpt_buforder_comparator(const CkptSortItem *fc_a, const CkptSortItem *fc_b)
{
	/* 比较表空间 */
	if (fc_a->tsId < fc_b->tsId)
		return -1;
	else if (fc_a->tsId > fc_b->tsId)
		return 1;
	/* 比较关系 */
	if (fc_a->relNode < fc_b->relNode)
		return -1;
	else if (fc_a->relNode > fc_b->relNode)
		return 1;
	/* 比较分支 */
	else if (fc_a->forkNum < fc_b->forkNum)
		return -1;
	else if (fc_a->forkNum > fc_b->forkNum)
		return 1;
	/* 比较块编号 */
	else if (fc_a->blockNum < fc_b->blockNum)
		return -1;
	else if (fc_a->blockNum > fc_b->blockNum)
		return 1;
	/* 相等的页面 ID 不太可能，但并非不可能 */
	return 0;
}

/*
 * 针对每个表空间检查点完成进度的最小堆的比较器。
 */
static int fc_ts_ckpt_progress_comparator(Datum fc_a, Datum fc_b, void *fc_arg)
{
	CkptTsStatus *fc_sa = (CkptTsStatus *) fc_a;
	CkptTsStatus *fc_sb = (CkptTsStatus *) fc_b;

	/* 我们想要一个最小堆，所以返回 1 代表 a < b */
	if (fc_sa->progress < fc_sb->progress)
		return 1;
	else if (fc_sa->progress == fc_sb->progress)
		return 0;
	else
		return -1;
}

/*
 * 初始化写回上下文，丢弃可能的先前状态。
 *
 * *max_pending 是一个指针而不是一个直接值，因此合并
 * 限制可以通过 GUC 机制轻松更改，因此调用代码不必
 * 检查当前配置。值为 0 表示不执行任何写回控制。
 */
void WritebackContextInit(WritebackContext *fc_context, int *fc_max_pending)
{
	Assert(*fc_max_pending <= WRITEBACK_MAX_PENDING_FLUSHES);

	fc_context->max_pending = fc_max_pending;
	fc_context->nr_pending = 0;
}

/*
 * 将缓冲区添加到待处理写回请求的列表中。
 */
void ScheduleBufferTagForWriteback(WritebackContext *fc_context, BufferTag *fc_tag)
{
	PendingWriteback *fc_pending;

	/*
	 * 将缓冲区添加到待处理写回数组中，除非禁止写回控制。
	 */
	if (*fc_context->max_pending > 0)
	{
		Assert(*fc_context->max_pending <= WRITEBACK_MAX_PENDING_FLUSHES);

		fc_pending = &fc_context->pending_writebacks[fc_context->nr_pending++];

		fc_pending->tag = *fc_tag;
	}

	/*
	 * 如果超过写回限制，则执行待处理的刷新。这
	 * 包括之前添加条目的情况，但现在已禁用控制。
	 */
	if (fc_context->nr_pending >= *fc_context->max_pending)
		IssuePendingWritebacks(fc_context);
}

#define ST_SORT sort_pending_writebacks
#define ST_ELEMENT_TYPE PendingWriteback
#define ST_COMPARE(a, b) fc_buffertag_comparator(&a->tag, &b->tag)
#define ST_SCOPE static
#define ST_DEFINE
#include <lib/sort_template.h>

/*
 * 将所有待处理的写回请求，之前通过
 * ScheduleBufferTagForWriteback 调度的，请发给操作系统。
 *
 * 因为这仅用于改善操作系统的 IO 调度，我们尽量避免
 * 出现错误 - 这只是一种提示。
 */
void IssuePendingWritebacks(WritebackContext *fc_context)
{
	int			fc_i;

	if (fc_context->nr_pending == 0)
		return;

	/*
	 * 按顺序执行写入可以使它们更快，并允许将
	 * 相邻块的写回请求合并为更大的写回。
	 */
	sort_pending_writebacks(fc_context->pending_writebacks, fc_context->nr_pending);

	/*
	 * 合并相邻的写入，但不涉及其他内容。为此，我们遍历
	 * 现在已排序的待处理刷新数组，并向前查找以
	 * 找到所有相邻（或相同）的写入。
	 */
	for (fc_i = 0; fc_i < fc_context->nr_pending; fc_i++)
	{
		PendingWriteback *fc_cur;
		PendingWriteback *fc_next;
		SMgrRelation fc_reln;
		int			fc_ahead;
		BufferTag	fc_tag;
		Size		fc_nblocks = 1;

		fc_cur = &fc_context->pending_writebacks[fc_i];
		fc_tag = fc_cur->tag;

		/*
		 * 向前查看后续写回请求，以确定它们是否可以
		 * 与当前请求合并。
		 */
		for (fc_ahead = 0; fc_i + fc_ahead + 1 < fc_context->nr_pending; fc_ahead++)
		{
			fc_next = &fc_context->pending_writebacks[fc_i + fc_ahead + 1];

			/* 不同文件，停止 */
			if (!RelFileNodeEquals(fc_cur->tag.rnode, fc_next->tag.rnode) ||
				fc_cur->tag.forkNum != fc_next->tag.forkNum)
				break;

			/* 好的，区块排队两次，跳过 */
			if (fc_cur->tag.blockNum == fc_next->tag.blockNum)
				continue;

			/* 只合并连续的写入 */
			if (fc_cur->tag.blockNum + 1 != fc_next->tag.blockNum)
				break;

			fc_nblocks++;
			fc_cur = fc_next;
		}

		fc_i += fc_ahead;

		/* 最后告诉内核将数据写入存储 */
		fc_reln = smgropen(fc_tag.rnode, InvalidBackendId);
		smgrwriteback(fc_reln, fc_tag.forkNum, fc_tag.blockNum, fc_nblocks);
	}

	fc_context->nr_pending = 0;
}


/*
 * 实现 TestForOldSnapshot 的较慢/较大部分
 *
 * 较小/较快的部分放在内联中，但整个逻辑集太大，无法做到这一点。
 */
void TestForOldSnapshot_impl(Snapshot fc_snapshot, Relation fc_relation)
{
	if (RelationAllowsEarlyPruning(fc_relation)
		&& (fc_snapshot)->whenTaken < GetOldSnapshotThresholdTimestamp())
		ereport(ERROR,
				(errcode(ERRCODE_SNAPSHOT_TOO_OLD),
				 errmsg("snapshot too old")));
}
